Cucumber voor unit testing

Cucumber voor unit testing

Cucumber wordt vaak gebruikt voor webtesting (i.c.m. Selenium). Ik focus op Cucumber en puur Java en wil je laten zien waarom ik zo enthousiast ben over Cucumber voor unit testing.

Cucumber introductie

Cucumber is een framework voor Behavior Driven Development (BDD). Cucumber gebruikt gewone taal om specificaties vast te leggen. Vanuit die specificaties kun je eenvoudig code genereren die je helpt om testen voor specificaties te schrijven. Want TDD en BDD gaan prima samen! Voorbeeldje:

Feature: Simple math

  Scenario: Addition
    Given the number 1
    And we add 1
    When calculating
    Then the result should be 2

Leesbaar, zelfs voor je oma (en als ze geen Engels kan lezen, het kan ook volledig in het Nederlands!). Ik ga niet heel Cucumber uitleggen. Simpel gezegd:

  • Een feature (functionaliteit) is een verzameling van een of meer Scenarios
  • Een regel (feature step) begint met een keyword (Given, When, Then, …) en daarachter vrije tekst
  • Nummers en tekst tussen “quotes” worden automatisch herkend als testparameters

Voor dit voorbeeld heb je een simpel maven project nodig. Op de site van Cucumber zie je hoe je dit doet.
Sla het Feature/Scenario voorbeeld op in src/test/resources/bdd/math/math.feature.

Feature class

Deze feature willen we implementeren in een Java class. Maak in dit geval een Java class in src/test/java/math/MathFeature.java. Daar gaan we zometeen mee verder.

Test class

Om deze feature met JUnit testen te laten draaien, moeten we een Test Class maken met de annotations @RunWith en @CucumberOptions. Dit mag volgens Cucumber niet de feature class zijn (probeer maar eens, Cucumber geeft hier een zinnige foutmelding op). Ik vond het jammer om naast m’n feature class een lege class (file) te moeten maken met twee annotations, dus ik doe het met een inner class:

public class MathFeature {
    @RunWith(Cucumber.class)
    @CucumberOptions(features = "src/test/resources/bdd/math")
    public static class Runner {}
}

De feature class implementeren

Nu we een runner hebben, kunnen we de unit test eindelijk draaien! Doe dat voordat je de feature class gaat implementeren, want Cucumber helpt je hier enorm door in de output de code te plaatsen die je kunt copy/pasten naar de feature class!

public class MathFeature {
    @Given("^the number (\\d+)$")
    public void the_number(int number) throws Throwable {
    }

    @Given("^we add (\\d+)$")
    public void we_add(int number) throws Throwable {
    }

    @When("^calculating$")
    public void calculating() throws Throwable {
    }

    @Then("^the result should be (\\d+)$")
    public void the_result_should_be(int expectedResult) throws Throwable {
    }

    @RunWith(Cucumber.class)
    @CucumberOptions(features = "src/test/resources/bdd/sum")
    public static class Runner {
    }
}

Met dit skelet kun je de unit test verder in gaan vullen, dat valt buiten de scope van deze post.

Stapje verder

Met unit testen zie je vaak hetzelfde scenario met steeds nieuwe parameters. Je kunt natuurlijk het scenario meerdere keren copy/pasten en vullen met andere getallen. In Java zou je dat refactoren. JUnit heeft hiervoor parameterized tests.

In Cucumber kun je dit ook refactoren, namelijk met abstracte scenario’s:

  Scenario Template:
    Given the number <base>
    And we add <addition>
    Then the result should be <expected result>

    Examples:
      | base | addition | expected result |
      | 1    | 1        | 2               |
      | 2    | 2        | 4               |
      | 100  | 100      | 200             |

Het scenario bevat parameters tussen < en > en heeft een tabel met voor elke parameter een kolom. Elke regel is een afzonderlijke testgeval. Handig: Eclipse en IntelliJ hebben Cucumber plugins die zowel de teksten als de tabellen formatteren! Geen lamme spatiebalk-duim dus!

Mockito

Mockito is een veelgebruikt mocking framework. Als je in je feature class mocking annotations wilt gebruiken, dan moet je Mockito zelf initialiseren, omdat we de @RunWith al hebben gebruikt voor Cucumber:

public class OtherFeature {
    @InjectMocks
    private SuperService service;

    @Mock
    private SomeDependency dependency

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }
}

Pas op! Je moet hier de @Before annotation uit cucumber.api.java gebruiken, niet uit org.junit!

Java 8

In Java 8 kun je je feature class ook anders implementeren. In dit voorbeeld kun je zien hoe je een step m.b.v. een lambda expressie maakt. Helaas zijn er wat problemen ontstaan met deze implementatie dus daar zullen we nog even op moeten wachten…

Waarom Cucumber unit tests?

Ik zie een aantal voordelen van de combinatie BDD/TDD/Unit tests:

  • TDD to the max! Je kunt beginnen zonder een regel code. Run tekst en schrijf (test)code tot je teKst slaagt.
  • Je unit tests worden gestructureerder. Je herstructureert en herbruikt zinsconstructies i.p.v. continue je code refactoren. Je testcode heeft dus meteen een betere structuur.
  • Iemand die jouw tests leest is er sneller in thuis.
  • Laat een ontwerper/tester zelf tests toevoegen om te kijken wat er gebeurt.

Met deze handvatten kun je een start maken met het schrijven van unit tests die voor iedereen leesbaar zijn. Natuurlijk geldt net als altijd: je hoeft niet al je unit tests op deze manier te schrijven. Begin er gewoon mee en laat me weten wat je er van vindt!

One thought on “Cucumber voor unit testing

  1. Ja, leuk he. Wij laten onze testers de cucumber tests opstellen i.c.m. de SME’s. Dit om te voorkomen dat wij als ontwikkelaars de specificaties fout begrijpen en de fout in zowel de ‘actual’ als in de ‘expected’ tegenkomen. Op die manier komt het 4-ogen principe weer terug in het testen.

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: