Ultra-tiny given-when-then DSL-snippet


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 ;)


comments powered by Disqus