Cucumber on user storyjen hyväksymistestauksen automatisointiin tarkoitettu kirjasto/työkalu.

Ideana on kirjoittaa storyjen testit asiakkaan ymmärtävässä muodossa, luonnollisella kielellä, mutta tehdä niistä kuitenkin automaattisesti suoritettavia.

Cucumber on behavior-driven development eli BDD -koulukunnan kehittämä kirjasto. BDD:ssä pyritään välttämään termin testi käyttöä ja sen sijaan puhutaan esimerkkitoiminnallisuuksista tai skenaarioista.

Vaikka Cucumber on varsinaisesti tarkoitettu koko ohjelmiston “end-to-end”-testaukseen, tarkastellaan ensin Cucumberin toimintaperiaatteita testaamalla yksittäistä luokkaa.

Hae kurssirepositorion hakemistossa viikko3/HelloCucumber oleva projekti.

Testattavana on yksinkertainen laskuri:

public class Counter {
   int val = 0;

   public void increase() {
        val++;
   } 
   
   public void reset() {
        val = 0;
   }    
   
   public void increment(int a) {
        val += a;
   } 
   
   public int value() {
       return val;
   }
}

Vaatimuksien ilmaiseminen

Laskurin haluttua toiminnallisuutta kuvaavat seuraavat user storyt

  • As a user I want to be able to increase the counter value
  • As a user I want to be able to set the counter to value zero

Cucumberissa (ja muutamassa muussakin BDD-työkaluissa) vaatimukset ilmaistaan Gherkin-formaatissa. User storyista, eli käyttäjän haluamista toiminnallisuuksista käytetään nimitystä feature. Laskimen storyt voidaan ilmaista seuraavasti:

Feature: As a user I want to be able to increase the counter value
Feature: As a user I want to be able to set the counter to value zero

Jokainen feature talletetaan omaan .feature-päätteiseen tiedostoon. Featuret sijoitetaan gradle-projekteissa hakemiston src/test/resources/ alle. Esimerkkiprojektissa featuret ovat tiedostoissa src/test/resources/ohtu/increasingCounter.feature ja src/test/resources/ohtu/resetingCounter.feature

Featureen liittyy joukko skenaarioita, jotka vastaavat käytännössä storyn hyväksymistestejä:

Feature: As a user I want to be able to increase the counter value

  Scenario: Increment once
    Given Counter is initialized
    When it is incremented
    Then the value should be 1

  Scenario: Increment by many
    Given Counter is initialized
    When it is incremented by 5
    Then the value should be 5

  Scenario: Increment many times
    Given Counter is initialized
    When it is incremented
    And it is incremented
    And it is incremented
    Then the value should be 3

Skenaariot taas kirjoitetaan Given, When, Then -formaatissa. Jokaista skenaarion riviä kutsutaan askeleeksi eli stepiksi.

  • Given kertoo skenaarion (eli testin) lähtötilanteen
  • When kuvaa operaation mitä skenaariossa testataan
  • Then ilmaisee mitä skenaariossa pitäisi tapahtua

Avainsanan And avulla jokaiseen skenaarion askeleista voidaan liittää useita steppejä. Näin tehdään esimerkin kolmannessa skenaariossa.

Testien suorituskelpoiseksi tekeminen

Jotta testeistä saadaan suorituskelpoisia, tulee projektiin kirjoittaa skenaarion steppejä vastaava koodi. Jokainen steppi määritellään omana metodina luokassa Stepdefs. Esimerkin steppien määrittely tapahtuu seuraavasti:

public class Stepdefs {
  Counter counter;

  @Given("Counter is initialized")
  public void counterIsInitialized() {
    counter = new Counter();
  }

  @When("it is incremented")
  public void itIsIncremented() {
    counter.increase();
  }

  @Then("the value should be {int}")
  public void theValueShouldBe(Integer val) {
    assertEquals(val.intValue(), counter.value());
  }

  @When("it is incremented by {int}")
  public void itIsIncrementedBy(Integer val) {
    counter.increment(val);
  }
}

Jokaista metodia edeltää annotaatio, joka määrittelee mitä steppiä vastaavasta metodista on kyse. Kaikkien skenaarioiden Given-step on sama, se määrittelee että skenaariot alkavat laskurin luomisella

@Given("Counter is initialized")
public void counterIsInitialized() {
  counter = new Counter();
}

Stepeissä voi olla “parametreja”, eli skenaariossa

Scenario: Increment by many
  Given Counter is initialized
  When it is incremented by 5
  Then the value should be 5

määritellyt luvut välitetään niitä vastaaville metodeille parametrina:

@When("it is incremented by {int}")
public void itIsIncrementedBy(Integer val) {
  counter.increment(val);
}  

@Then("the value should be {int}")
public void theValueShouldBe(Integer val) {
  assertEquals(val.intValue(), counter.value());
} 

Onnistumisen varmistava Then-step suorittaa tarkastuksen JUnitin assertEquals-metodia käyttäen.

Cucumber edellyttää vielä pienen määrän konfiguraatiota, joka on tehty tiedostossa src/…/RunCucumberTest.java. Konfiguraatio on yksinkertainen, se määrittelee mistä hakemistosta feature-tiedostot löytyvät, sekä sen että testien tulos raportoidaan komentorivillä pretty-formatterin avulla:

@RunWith(Cucumber.class)
@CucumberOptions(
  plugin = "pretty", 
  features = "src/test/resources/ohtu", 
  snippets = SnippetType.CAMELCASE 
)
public class RunCucumberTest {}

Määrittely on sikäli hämmentävä, että määriteltävä luokka RunCucumberTest ei sisällä mitään koodia, ja kaikki oleellinen määrittely tapahtuu luokkaan liitetyn annotaation @CucumberOptions parametreina.

Testit suoritetaan komennolla gradle test.

Huomaa, että testien suorittaminen ei todennäköisesti toimi NetBeansin testinapilla.

Nollaamisen skenaariot

Laskimen nollaamiseen liittyvä story on tiedostossa src/test/resources/ohtu/resetingCounter.feature

Lisää storyyn seuraavat skenaariot:

Feature: As a user I want to be able to set the counter to value zero

  Scenario: Resetting after one increment
    Given Counter is initialized
    When it is incremented
    And it is reset 
    Then the value should be 0

  Scenario: Resetting after incrementing with several values
    Given Counter is initialized
    When it is incremented by 5
    And it is reset 
    Then the value should be 0

Kun nyt suoritat testit komennolla gradle test näyttää tulos seuraavalta:

Cucumber maalaa stepin And it is reset keltaisella ja kertoo virheestä

io.cucumber.junit.UndefinedStepException

Cucumber siis ilmoittaa osan stepein olevan määrittelemätön. Avaamalla selaimella virheilmoituksen tarkempi raportti, näyttää Cucumber valmiin metodirungon, jonka avulla stepin voi toteuttaa:

Kopioi stepin koodirunko

@When("it is reset")
public void itIsReset() {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}

luokkaan Stepdefs ja täydennä se järkevällä tavalla.

Varmista että testit toimivat.