London Style TDD aka Mockist Approach

London Style TDD aka Mockist Approach
februari 22, 2013 squeedconfig

GothPy User Group Logotype

Igår huserade Göteborgs Python-användargrupp, GothPy, i Squeeds lokaler och temat för kvällen var ”London Style TDD”. En annan term som också använts för samma(?) stil eller variant av TDD är Mockist approach. För dem av som sitter i en statiskt/starkt typad, kompilerad miljö, tycker jag att detta är en teknik man skall förstå och gärna behärska – för varje software craftsman (eller kanske software crafts-hen) så bör detta vara en teknik eller ett verktyg bland flera, det är ett ganska tydligt tillvägagångssätt som används för att mejsla fram mjukvara.

Vi var dryga dussinet, åt många ovanligt små pizzor från Pizza Hut och hade oväntat mycket diskussioner kring mycket: jag hade väntat mig mindre diskussion och mer kodande. Jag tyckte dock det var mycket hög nivå på diskussionerna och många bra sidor och argument lyftes fram, vilket jag tror hela gruppen upplevde eftersom det var få som uttryckte någon önskan att ”de ville gå vidare”. Lite oväntat för mig var den klart intressanta och underhållande dissektionen av klassificering av Test Doubles (alltså vad är egentligen dummies, stubs, fakes, spies, mocks?). Utan att gå in på detaljer så tog jag med mig en något rubbad uppfattning om klassisk TDD jämfört med mockbaserad TDD (classic vs stubborn).

Före

Mina två huvudsakliga åsikter som jag hade med mig in i rummet var de följande.

Klassisk = tillståndsbaserad testning, Mockbaserad = beteendebaserad

Jag är av uppfattningen att mängden testkod i ett icke-trivialt system kommer vara lika stor oavsett om man kör klassisk eller mockbaserad stil.

Den ”stora” skillnaden är att fake-implementationerna behöver vara tillräckligt tillståndsfulla för att tillåta inspection så att man kan stämma av tillstånd i testens assertions. Dessutom behöver fake-implementationerna uppfylla beteendedelen av de kontrakt som finns. I starkt typade språk (Java) innebär det inte sällan komplicerade implementationer för att kunna nå gränsfall, eller multipla fake-implementationer.

Med mockbaserade test-doubles är tillstånd aldrig intressant och därför har man ”bara” en aspekt att bry sig om när man skriver sina test doubles – beteende-/kontraktsaspekten.

Detta blir intressant i fallet när kodbasen rör på sig, gränssnitt och beteenden förändras. Med fake-implementationer behöver man ändra beteenden + sin tillstånds-implementation (som behövs för att kunna göra asserts på sluttillstånd) så att den fortfarande matchar originalets beteende. Med mock-implementationer behöver man ”bara” ändra beteenden.

(Borde det inte kunna bli mindre kod rent av med mockning då, om man nu inte behöver lagra state någonstans? Jo, det kan man ju tycka från argumentationen ovan, men det inte var jag har kunnat se. Radmässigt blir möjligen testen i klassisk stil något kortare (man behöver inte välja mockarnas beteenden – fake-implementationernas default-beteenden är nog för de flesta test), men å andra sidan hamnar mer kod i test double-implementationerna.

Självklart underhåller man sin testkod med samma kärlek och omsorg som produktionskod, vilket gör att stubbning av mockobjekt – i större system – hamnar i ”MockFactories” där man placerar återanvänd kod. Jag förordar inte stilen där varje mocks beteende skall uttryckas explicit i varje test – ”self containing test”. Jag ser inte varför man skall lita mer på kod som ligger i test doubles av typen fake, jämfört med av typen mock. (Det argumentet låter som skrock och rädsla för det okända: ”jag litar mer på min kod i min fake, än på mockito-ramverket” t ex.

I Python är behovet av mockramverk mycket litet

Min bild har varit att eftersom alla objekt är ”kontraktslösa”, i meningen att allt är löst typat (Duck typing), därför gör det billigt* att skapa test doubles av olika slag, när de behövs, där de behövs och på det sätt de behövs.
I Java-språket med stark typning måste man skicka objekt som uppfyller kontrakt, medan enhetstest sällan har behov av fullvärdiga implementationer och än mindre utnyttjar enheten under test (SUT) hela, eller ens stora delar av, kontrakten från sina collaborators i enskilda test.

Efter

Ett bra argument jag själv helt förbisett (vilket är en klar hint om hur ofta jag jobbar med Python) är att ett bra mockramverk kan ge bra feedback när man utvecklar (”Expected %s invocations of %s, got %s”) vilket ju rättfärdigar nyttan med ett mockningsramverk.

Stubbar man mockobjekt, eller definierar man kontraktuella beteenden hos sina collaborators under viss interaktion (ett tests livscykel) med följande kod:

when(collaboratorA.reserveSeats(10)).thenThrow(new RuntimeException("database is down"));
// or
when(collaboratorA.cancelReservation("1234")).thenReturn(new ReservationCancellation());

En terminologigrej tror jag – jag har inte använt ordet stub utan använt andra formuleringar (förbereder, ”berättar för mockobjektet hur det ska bete sig” t ex).

Jämför med BDDMockito-smaken:

given(collaboratorA.reserveSeats(10)).willThrow(...);
// or
given(collaboratorA.cancelReservation("1234")).willReturn(...);

En annan sak jag vill fundera mer på var rekommendationen att inte mocka 3pp, ”don’t mock what you don’t own” tror jag var originalfraseringen. Den diskussionen gick lite för fort för mig och ambitionen att ”bara lägga på ett till lager” mellan plattformens eller 3pp-ramverkets API:er och det man själv vill använda minimerar möjligen kontaktytan (och framtvingar ett mer explicit ställningstagande i hur beroende man vill vara av 3pp-saken), men flyttar i viss mån bara runt problemet till det nya lagret.

Bevis

Nej, det finns givetvis inte. Kod är i stora delar ett hantverk och då är världen sällan skalan svart eller vit.

Jag började för ett litet tag försöka skriva ett exempel på hur kod kan se ut med mockbaserad teststil, jämte klassisk stil. Dock inte i Python. Jag är en bit från ”klar”, exemplet är inte heller perfekt men jag tror intentionerna synas i det som finns där nu. https://github.com/FredrikWendt/tdd-classic-vs-mockist/tree/master/src/test/java/se/wendt/tdd

Slutsats

GothPy är en härlig samling med trevliga och mycket kompetenta människor från ganska varierande och olika bakgrund. Mixen är klockren och ger alltid härligt nyanserade diskussioner med högt i tak och genuin vilja att utforska olika riktningar och synsätt.

Pga denna gruppdynamik kände jag att det fanns mycket mer att fundera kring, testa (koda) och gräva i på detta ämne och jag åkte hem på vagnen utan att känna mig riktigt ”färdig”! 🙂

* Billig kod är lätt att läsa, förstå (lagom kort och koncis) och underhålla. Den kräver också mycket lite kognitiv belastning hos läsaren.

0 Kommentarer

Lämna ett svar

E-postadressen publiceras inte. Obligatoriska fält är märkta *

*

Denna webbplats använder Akismet för att minska skräppost. Lär dig hur din kommentardata bearbetas.