Fra enhentstester til kjørbare spesifikasjoner

Dan NorthDet er snart et år siden Dan North var i Bergen og snakket om Behaviour-Driven Development (BDD) - et besøk jeg bl.a. dokumenterte i the Contiki Strip #5. Det var et utrolig inspirerende møte, og Given-When-Then spesifikasjonformatet var noe vi prøvde å jobbe med i tiden etterpå. Men siden vi ikke hadde kommet orntlig igang med å skrive enhetstester, og var langt fra å praktisere TDD, glemte vi det raskt ut, og alt forble ved det gamle.

Nå som jeg har jobbet mer med testdreven utvikling er det på tide å ta dette opp igjen. Jeg vil derfor presentere hvordan man kan gå fra “vanlige” unit-tester til å implementere kjørbare spesifikasjoner (the BDD way), og snakke litt om hvilke fordeler dette gir oss.

Ta f.eks. denne enhetstesten, hentet fra Robert C. Martins bok Agile Principles, Patterns, and Practices in C#:

    1 [Test]

    2 public void DeleteEmployee()

    3 {

    4     int empId = 4;

    5     AddComissionedEmployee t =

    6         new AddComissionedEmployee(empId, “Bill”, “Home”, 2500, 3.2);

    7     t.Execute();

    8 

    9     Employee e = PayrollDatabase.GetEmployee(empId);

   10     Assert.IsNotNull(e);

   11     DeleteEmployeeTransaction dt = new DeleteEmployeeTransaction(empId);

   12     dt.Execute();

   13 

   14     e = PayrollDatabase.GetEmployee(empId);

   15     Assert.IsNull(e);

   16 }

Denne koden har utstract bruk av COMMAND pattern. Testen oppretter først en Employee i databasen (line 5-7), og bekrefter at den ble opprettet (line 9-10). Deretter sender den en slette-komando (linje 11-12) og verifiserer at den nå ikke finnes i databasen lengre (linje 14-15). En helt normal enhetstest etter min mening.

Den uttrykker derimot ikke så mye om intensjonen bak testen - man må lese koden for å forstå hva som skjer, og strukturen er helt og holdent opp til utvikleren. En bedre måte å gjøre dette på er å sette opp et Given-When-Then-senario: Gitt at vi har en kunde, Når vi ber om å få den slettet, finnes den ikke lengre i databasen.

Implementert vha. TinyBDD, et rammeverkt for å lage BDD-spesifikasjoner under utvikling av Gjøran Hansen, forvandles enhetstesten til noe sånn som dette:

    1 [Test]

    2 public void DeleteEmployeeTransaction_should_remove_employee()

    3 {

    4     int empId = 4;

    5 

    6     Scenario.New(“DeleteEmployeeTransaction should remove employee”,

    7         scenario =>

    8     {

    9         scenario.Given(“An Employee with a specific ID”, () =>

   10             new AddComissionedEmployee(empId, “Bill”, “Home”, 2500, 3.2).Execute())

   11 

   12                 .And(“It exists in the repository”, () =>

   13                     Assert.IsNotNull(PayrollDatabase.GetEmployee(empId)));

   14 

   15         scenario.When(“A delete transaction is sent with that ID”, () =>

   16             new DeleteEmployeeTransaction(empId).Execute());

   17 

   18         scenario.Then(“The Employee should no longer exist in repository”, () =>

   19             Assert.IsNull(PayrollDatabase.GetEmployee(empId)));

   20 

   21     }).Execute();       

   22 }

Om du ikke er vandt med lambda-uttrykk i C# er dette sikkert ganske gresk, men gi det en sjanse likevel. I linje 6 opprettes et nytt senarie. Så på linje 9 sier vi at hvis (Given) en bestemt Employee existerer, og (på linje 12) han finnes i databasen, når (When) man da eksekverer en slette-kommando (linje 15), så (Then) vil Employee’en ikke lenger finnes i basen (linje 18).

Lambda-uttrykkene på linje 10, 13, 16 og 19 utfører og/eller tester det vi har spesifisert i kallene til Given, When og Then. Og sammenligner du med den originale testen så ser du at de egentlig gjør akkurat det samme.

Forskjellen er at BDD-spesifikasjonen beskriver/dokumenterer i klartekst hva situasjonen er og hvilken adferd vi er ute etter. Og spesifikasjonen er så nær koden at det ikke vil være noe problem å holde dem og koden synkronisert om den skulle endres. Den er relativt oversiktelig, og følger et fast mønster.

Og den virkelige gevinsten oppdager du når du innser at disse spesifikasjonene kan skrives av eller i sammarbeid med testere, product owners og til og med kunden selv. Spranget fra kundens eller produktets krav til selve koden blir minimal. Utvikleren og kunden kan f.eks. sitte sammen og skrive spesifikasjonen rett inn i en .cs-fil.

Når spesifikasjonene er definert kan man så kjøre dem underveis i prosjektet, og ta ut rapporter som i klartekst beskriver senariene, og hvilke av dem som er ferdig implementert, hvilke som gjenstår, og eventuelt hvilke som feiler (TinyBDD støtter ikke dette enda såvidt meg bekjent).

BDD er mye mer enn det jeg har vist her. For en praktisk introduksjon til et annet BDD rammeverk som heter MSpec anbefaler jeg at du tar en titt på Rob Conery’s screencast om BDD - meget inspirerende. Og for mer informasjon om BDD generelt er behaviour-driven.org en bra ressurs. Det har vært mye forvirring rundt testdreven utvikling, og BDD gir en litt annen vinkling på problemet, og et sterkere fokus på adferd, noe som vil kunne øke kvaliteten på programvaren du produserer.

Knagger: , , ,

Kategorier: Softwareutvikling, Testing / TDD.
RSS feed for kommentarene. Tilbaketråkk.

2 kommentarer til “Fra enhentstester til kjørbare spesifikasjoner”

  1. Gøran Hansen Says:

    Hei Torbjørn

    Takk for at du nevnte TinyBDD i posten. Håper du fortsatt bruker det og er fornøyd med det:)? Jeg holder på å jobbe med noen oppdateringer som vil komme i løpet av en uke eller to.

    Gøran

  2. Torbjørn Says:

    Ja, jeg bruker TinyBDD aktivt. Rammeverket er en meget smertefri innfalsvinkel til BDD og bedre tester generelt, og jeg ser frem til nye oppdateringer. Keep up the excellent work!

Skriv en kommentar

Torbjørn Marø

Torbjørn er systemutvikler og et aktivt medlem av .NET-miljøet i Bergen. Dette er hans blog.

Mulig relaterte linker

Siste kommentarer


Torbjørn: Kan alltid stole på at Ameth kommer med en fungerende løsn...

Ameth: Enumerable.Range(1, 11).Select(i => (Math.Pow(1+Math.Sqrt...

Torbjørn: Ja, Linq og lambda i C# har mange bruksområder, og gir komp...

Kenneth: Innså til slutt at å produsere fibonacci-følgen med en sl...

Kenneth: Dette er ikke relatert til verken mønstergjenkjenning eller...

Torbjørn: Note to self: I Clojure heter denne funksjonen mapcat...

Torbjørn: Nei, vi bruker ikke Clojure, og inntil videre lærer jeg meg...

 Hold deg oppdatert

Søk i bloggen

  • Follow me on Spotify

    Kategorier

  • .net ninja (26)
  • Bøker (10)
  • Diverse prosjekter (22)
  • Erlang (7)
  • F# (2)
  • Hardware (1)
  • Jobb (64)
  • kjempekjekt.com (16)
  • LISP/Clojure (11)
  • NNUG / community (39)
  • O/RM & databaser (9)
  • Off topic (111)
  • OO-design/clean code (19)
  • Podcasts (10)
  • Polyglot (23)
  • Ruby (18)
  • Silverlight / RIA (3)
  • Software/verktøy (16)
  • Softwareutvikling (14)
  • Testing / TDD (25)
  • the contiki strip (13)
  • User experience (3)
  • WCF (3)
  • Webutvikling (20)
  • WPF (9)
  • WTF (4)
  • Abonner via epost

    Om du vil kan du få alle nye blogposter tilsendt til din epost. Abonner nå, det er kjempeenkelt!

    Mine bokmerker

    Meta