TDD + DDD = BDD

 
09 juni 2009

In vervolg op mijn vorige post over TDD vs DbC wilde ik verder uitleggen waarom ik vind dat TDD iets anders is dan alleen unit tests. Sterker nog: TDD heeft in het begin helemaal niets met unit tests te maken, volgens mij. In den beginne heb je namelijk nog helemaal geen units.

Bij het modelleren van een domein begin je namelijk typisch met uitspraken als: “Als een hypotheekaanbieder een voorwaarde stelt, dan geldt deze in principe altijd voor alle productlijnen en hypotheekproducten die deze aanbiedt, tenzij deze op één van deze lagere niveaus overschreven wordt” of “Als een bedrag van 20 euro van een rekening met 100 euro naar een pas geopende rekening overgeschreven wordt, dan bevindt zich, als alles goed gaat, 80 euro op de eerste rekening en 20 euro op de tweede na de overschrijving.”

Welke units gaat dit over? Juist, dit gaat over units heen. Hieruit gaan wellicht units volgen, maar op dit moment zijn ze er nog niet en uit deze uitspraken volgt het gebruik van meerdere units tegelijk, die samen bepaald gedrag vertonen. Dit is dan ook het uitgangspunt van behaviour driven development (BDD), dat probeert het originele TDD meer in lijn te brengen met DDD. Ja, leuk al die afkortingen. :-) De basis van het BDD proces bestaat uit de start met User Stories. Hieronder zie je een voorbeeld hiervan in een standaard formaat, uit de bovenstaande wiki overgenomen:

A SubjectMatterExpert (typically a business user) works with a BusinessAnalyst to identify a business requirement. This is expressed as a story using the following template:

As a Role
I request a Feature
To gain a Benefit

The speaker, who holds the Role, is the person who will gain the Benefit from the requested Feature.

Een dergelijke user story resulteert in verschillende scenarios, waarbij je gebruik kunt maken van de volgende template om deze scenario’s uit te werken:

Given some initial context (the givens),
When an event occurs,
then ensure some outcomes.

bron: http://dannorth.net/introducing-bdd

Het bovenstaande past heel goed bij het ontwikkelen van een domein model in een dynamische taal en bij uitstek in Smalltalk. Je kunt deze zinnen als methode naam kiezen en de tekst vervolgens gebruiken als basis voor de implementatie. Het bijzondere is dan natuurlijk dat de Smalltalk IDE je in staat stelt om het scenario direct uit te voeren en de classes en methodes ‘on the fly’ in te vullen tijdens het debuggen van het scenario. Het is dan ook niet toevallig dat we dat hier binnen Sogyo vaker doen…

Inmiddels zijn mijn gedachten over wanneer het handig is om in een dynamische taal te ontwikkelen en wanneer in een statische taal,  zich aan het refactoren (ik heb er weinig invloed op, dus voor zover ik weet doen de gedachten het zelf). Mijn voorlopige indruk is dat de ontwikkelingsfase van een domeinmodel in ieder geval een fase is waarin een dynamische taal veel voordelen biedt. Verdere lijken mij de dynamiek van het domein in kwestie en de noodzaak binnen het domein om runtime exceptions uit te sluiten, van invloed op de keuze voor een dynamische of statische taal, maar mijn ideeën daarover hebben zich nog niet helemaal gevormd.

Ik eindig even met een vraag: zou het misschien ideaal zijn om te beginnen in een dynamische taal om van buitenaf, black-box te kunnen specificeren middels BDD en nadien, als het model zich begint te ‘settelen’, vanaf de basis steeds meer DbC toe te passen, waarbij het gebruik van een statisch typeringssysteem daar dan volgens mij onder valt. Als ik denk het antwoord op deze vraag gevonden / gehoord te hebben, is het wellicht tijd voor een nieuwe blogpost!


Werken met ?
Kijk dan bij onze mogelijkheden voor zowel starters als ervaren engineers.


Categorieën: Development

Tags: , , ,


Reacties (7)

  • Mooie beschrijving van je (test) werkwijze, Mendelt. En inderdaad akelig dicht bij wat ik zelf doe, inderdaad. :-)

    ‘Unit’ tests op het niveau van aggregates komt mij bekend voor. Mijn ervaring is, is dat onder dat niveau vaker refactoring plaatsvindt en dat die vertraagt wordt door het toevoegen van testen, terwijl die testen weinig tot geen unieke functionaliteit testen.

    Ook het probleem met de ‘integratie’ testen herken ik wel. Toch wel goed om te proberen, maar vaak wordt het toch te traag om vaak te gebruiken. Maar het voorkomt in ieder geval wel dat je de bruikbaarheid van de applicatie als geheel over het hoofd ziet.

    Wat betreft de BDD stijlen zal ik nog eens verder lezen, neem het op dit moment van je aan. :-)

    Geplaatst op 24 juni 2009 om 12:21 Permalink

  • Wat je nu beschrijft komt redelijk overeen met hoe ik dingen aanpak. Hoewel ik meestal presenter-first werk om te zorgen dat ik mijn domein niet over-design. Maar dat is weer een hele andere discussie :-)

    Wat bij opvalt is dat na verloop van tijd wel units opduiken in m’n tests. Ook als ik vanuit simpele beweringen over het domein begin. Vaak zijn ‘units’ groepjes klassen. En vaak komen ze redelijk overeen met ‘aggregates’ in mn domein. Meestal laat ik dat ook terugkomen in de manier waarop ik mijn tests indeel. Dat kost wat refactorwerk maar is meestal wel de moeite waard.

    Persistance ignorance in je domein (en dus in je tests) is volgens mij bijna een voorwaarde om dit goed te doen.

    Niet al mn tests slaan echter op mijn domein, ik test presentatie-logica en services ook. En niet alle tests in mn domein zijn van belang voor mensen die op user-story niveau naar kijken naar het domein. Ook probeer ik vaak nog wat ‘integratie tests’ te automatiseren, hier test ik de hele applicatie inclusief dal, services etc. Dit lukt me echter niet altijd even goed :-)

    Meestal deel ik tests op op 2 manieren op.

    Tests per component (domein, presentatie, dal, service), en soort test, op user story niveau heb ik meestal een hoop tests, die dingen horen als een soort spec’s te lezen. Verder heb ik een hoop tests die op lager niveau implementatiedetails testen, volgens mij kan hier DbC en code contracts ook goed helpen, maar hier wil ik nog meer mee spelen. En ik heb een zooi intergratie tests. Deze zijn in de praktijk echter vaak onleesbaar en lastig te onderhouden door de grote hoeveelheid setup ed.

    Het BDD verhaal was voor zover ik me kan herinneren als volgt (correcties zijn welkom) Dan North heeft BDD bedacht als een manier om heel leesbaar story of acceptance tests op te schrijven. Volgens mij heeft ie jBehave hiervoor uitgevonden.
    Hierna is David Astels in Ruby begonnen met rSpec. David Astels BDD lijkt echter veel meer op TDD, volgens mij wordt deze stijl BDD ook wel Context/Specification genoemd. En volgens vullen beide stijlen elkaar aardig aan. Volgens mij groeien ze ook langzaam naar elkaar toe.

    Geplaatst op 18 juni 2009 om 9:35 Permalink

  • Dat BDD tests overeenkomen met de XP user tests ben ik met je eens. Dat ze fragiel worden kan ik ook beamen, ze zijn natuurlijk aan je hele domein gekoppeld. Daarentegen worden unit tests soms in hun geheel overbodig (bij wegvallen unit), dat zal je bij een test over het domein heen niet zo snel gebeuren.

    Dan zijn er ook nog heleboel mensen die vinden dat de unit van unit tests slaat op het domein als geheel, die dan gezien wordt als één logische unit. Bovendien, als je zorgt dat het verder niet gekoppeld is aan DB en web services, is het ook snel, één van de redenen om tests als unit tests te schrijven. Ook wat voor te zeggen.

    Ik denk dat het bovenstaande de reden is dat BDD acceptance tests door een aantal ontwikkelaars gelijkgetrokken worden met (leesbare) unit tests.

    Het verschil tussen de beide BDD kampen heb ik nog niet echt kunnen ontdekken. Wat is die volgens jou?

    Wat ik tegenwoordig doe is dat ik begin met behoorlijk uitgebreide beweringen over het domain, noem het BDD, noem het acceptance tests. Als de structuur zich enigszins settled, na de eerste fase, wordt het tijd om stukken in de code met (een uitgebreide hoeveelheid) logica te unit testen, dus op lager niveau. Heb ook geleerd om niet teveel tests te schrijven. Elke test kost ook wat (aan ontwikkeling, onderhoud, testtijd) en moet zijn kosten wel opbrengen door iets wezenlijks te testen dat nog niet getest wordt.

    Geplaatst op 18 juni 2009 om 7:01 Permalink

  • Volgens mij vergeet je iets belangrijks.

    Er zijn twee soorten tests van belang.

    Als eerste heb je tests die voor de klant/gebruiker/stakeholder/whatever van belang zijn. Als je alles goed doet zijn dit tests die als je ze goed opschrijft als requirements lezen. Dit zijn typisch geen unit tests want ze gaan idd over units heen. Oppassen dus want als je dit soort tests automatiseert kunnen ze wat fragieler worden. Dit is wat vaak met BDD wordt aangeduid. In XP worden dit acceptance tests genoemd en zelfs vaak door de on-site customer gemaakt. Deze tests zitten over het algemeen op user-story niveau.

    TDD gaat volgens mij wel over unit tests. Je kan TDD unit tests nog steeds leesbaar maken. Maar hier worden geen requirements in vastgelegd, wel ontwerpbeslissingen. Dit zijn de tests die je minuut tot minuut tijdens het bouwens schrijft.

    De spraakverwarring is onder andere ontstaan doordat bepaalde mensen de naam BDD, die oorspronkelijk werd gebruikt voor een bepaalde stijl acceptance testing, hebben gekaapt voor het schrijven van leesbare unit tests. Voor zover ik tot nu toe heb kunnen uitvogelen heb je aan de ene kant het xBehave kamp en aan de andere kant het xSpec kamp.

    Belangrijk is natuurlijk dat je beide soorten tests gebruikt en niet hoe je ze noemt.

    Geplaatst op 16 juni 2009 om 11:03 Permalink

  • André, bedankt voor je goede feedback. Ben benieuwd naar je implementatie.

    Even wat betreft het gebruik van een dynamische taal: in het begin schrijf je je BDD-tests in een nog niet bestaande taal, daar kan een statisch getypeerde taal niet zo goed tegen (je weet wel, de hele tijd ‘create method stub’ aanklikken enzo, als VS het niet al voor je aanvult, fout.

    Daarnaast is in het begin aantoonbare correctheid van de code nog niet zo belangrijk, aangezien de kans dat de code nog een heel andere kant op gaat groot is. Je zou dan bewezen correcte code hebben die zeker weten niet goed werkt omdat het niet doet wat het moet doen (vrij naar Dijkstra).

    Geplaatst op 11 juni 2009 om 21:00 Permalink

  • Rick,
    Mooie weergave van een activiteit die ik “programmeren” noem :). Je laat de software vanuit de requirements ‘groeien’. Ik denk persoonlijk niet dat een dynamische taal hiervoor nodig is (maar dat ligt waarschijnlijk aan mijn relatieve bekendheid met talen als Java/C#).
    Wat ik hiervan een heel interessant bijverschijnsel vind is dat ik vermoed (maar ik kan dat wederom niet onderbouwen) dat je als je deze methode op een goede manier toepast je niet globaal zou hoeven modelleren; alleen locaal in je code per user story. Je model ontstaat vanzelf vanuit de implementatie. Het begint bijna op een rule-engine te lijken…

    Toevallig ben ik wel bezig om een meer expliciet stappenplan / algoritme op te tekenen dat als kapstok kan dienen om dit soort requirements (user stories) om te zetten in code. De expliciete uitwerking (zal wel weer C# worden ;)) is wellicht een interessante om te gaan vergelijken met een ander platform? Ik kom erop terug :). Coole post.

    Geplaatst op 11 juni 2009 om 7:05 Permalink

    • Home schreef:

      in his Google video, there are still a high percentage of ppleoe who practices bad TDD. But there is also ppleoe who is practicing BDD while their doing TDD. So what is the difference? The one thing I have seen is that most ppleoe think that TDD is about Testing. When you explain that TDD is really at its hart a design process, they squawk at you and give you a look like you’ve lost your mind.I see TDD as performing the following roles:1. Design process2. Requirements Capturing3. Behavior Specification4. Regression Test SuiteFor this reason I think we need a Behavior-Driven Development framework.My daily development environment is .NET and C#, thus I would like to have something that suites my needs. Dave is working on rSpec for RoR and then there is JBehave for java. But I was unable to find anything for the .NET space. So I thought, I will roll my own.Here is what I have so far:using NBehave;namespace SampleBehaviour{ [ Functionality ] public class CustomerLoadBehaviour { private Customer customer = null; [InitializeSpecification()] public void Setup() { customer = new Customer(123); } [ Specification() ] public void ShouldLoadCustomer() { Behaviour.Of( customer.Id ).Must.Not.Be.Null( The Customer Id must not be null. ); Behaviour.Of( customer.Id ).Must.Equal( 123, The customer id’s aren’t equal ); } [ Specification() ] public void LoadCustomerShouldFailed() { Behaviour.Of(customer.Id).Must.Not.Be.Null( The Customer Id must not be null. ); Behaviour.Of( customer.Id ).Must.Not.Equal( 125, The customer id’s aren’t equal ); } }}I’m busy working on a TestTip for VS2005, but have finished the NUnit integration.

      Geplaatst op 05 april 2012 om 14:01 Permalink