torsdag 13. august 2009 Software/verktøy Testing / TDD
Jeg har testet diverse TDD/BDD-rammeverk de siste månedene (bl.a. machien specification og tinyBDD), og eksperimentert endel med hvordan jeg kan skrive tester/spesifikasjoner som dokumenterer koden best mulig. Dette har resultert i at jeg ikke bruker noe spesielt rammeverk i det hele tatt, men bare bruker deskriptive navn og et generelt given-when-then formular.
Mine tester har en stund nå sett ut omtrent som dette:
[Test]
public void Stupid_car_start_1()
{
Given_transmission_is(manual);
Given_transmission_is_in(first_gear);
Given_clutch_pedal_is(not_engaged);
When_key_is_turned();
Then_car_should_jump_forward();
Then_engine_should_be_dead();
}
Det virker litt teit å si given tre ganger på rad. Alternativt kunne given nummer to og tre i stedet begynt med "And_", og det samme kunne jeg gjort med Then_engine_is_dead(). Det er derimot et problemer med den måten å gjøre det på; ofte vil den metoden som er given nummer 3 i én test plutselig bli den eneste given i en annen test, og da kan den ikke hete noe med "And".
Et annet problem er at det som er en "When" i én test kan være kjekk å bruke som en "Given" i neste test. For å løse dette problemet legger jeg til et lite sett med properties i test-klassen:
#region Really tiny BDD DSL / Syntactic sugar
protected CarStartTests Given { get { return this; } }
protected CarStartTests When { get { return this; } }
protected CarStartTests Then { get { return this; } }
protected CarStartTests and { get { return this; } }
#endregion
Her ser du hvordan den forrige testen blir seende ut ved å bruke de nye propertiene:
[Test]
public void Stupid_car_start_2()
{
Given.transmission_is(manual);
and.transmission_is_in(first_gear);
and.clutch_pedal_is(not_engaged);
When.key_is_turned();
Then.car_should_jump_forward();
and.engine_should_be_dead();
}
Men nå står jeg også mye mer fritt til å trikse å mikse med metodene og variablene i test-klassen, samtidig som jeg beholder et flytende og naturlig språk. Samme testen kunne f.eks. lett se ut som dette:
[Test]
public void Stupid_car_start_3()
{
Given.vehicle.Transmission = manual;
and.vehicle.Transmission.SetGear(first);
and.vehicle.Clutch.ChangeState(not_engaged);
When.key.Turn();
Then.car_jump_counter.should_be(1);
and.vehicle.Engine.State.should_be(dead);
}
I realiteten blir det som regel en blanding av de to fremgangsmåtene: Givens skjuler ofte mye, som f.eks. oppsett av mocks/fakes, og beholdes som metoder. Thens på den andre siden er som oftest minst like lesbare om de er tester direkte på en eller annen variabel brukt for å "sanse" korrektheten av testen/spesifikasjonen. Kombinert med et sett med fluent asserts (som du finner i coreTDD) blir en sjekk av en variabel like lesbar som et metodenavn.
Nedenfor er en Visual Studio snippet-fil jeg har definert for å enkelt kunne inserte de fire BDD-propertiene i nye testklasser. Hvis du lagrer XML'en med navnet bdd.snippet under "My Documents\Visual Studio 2008\Code Snippets\Visual C#\My Code Snippets", så vil du kunne bruke den ved for eksempel å skrive "bdd" i koden din (uten hermetegnene altså..) og trykke TAB to ganger.
1 <?xml version="1.0" encoding="utf-8" ?>
2 <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
3 <CodeSnippet Format="1.0.0">
4 <Header>
5 <Title>Given-When-Then Syntax Sugar</Title>
6 <Shortcut>bdd</Shortcut>
7 <Author>http://blog.kjempekjekt.com</Author>
8 <Description>Provides a really tiny dsl for doing bdd.</Description>
9 </Header>
10 <Snippet>
11 <Declarations>
12 <Object Editable="false">
13 <ID>ClassName</ID>
14 <Function>ClassName()</Function>
15 </Object>
16 </Declarations>
17 <Code Language="CSharp">
18 <![CDATA[
19 #region Really tiny BDD DSL / Syntactic sugar
20 protected $ClassName$ Given { get { return this; } }
21 protected $ClassName$ When { get { return this; } }
22 protected $ClassName$ Then { get { return this; } }
23 protected $ClassName$ and { get { return this; } }
24 #endregion
25 ]]>
26 </Code>
27 </Snippet>
28 </CodeSnippet>
29 </CodeSnippets>
Hvis du finner noe mindre BDD-rammeverk enn dette så må du gi meg et vink ;)