Enhetstester och kraften i ett namn

Squeeds Julkalender | 2020-12-02 | Niklas Lindblad
Vad borde den här koden göra? Det är en fråga som öppnar många dörrar och hjälper oss fokusera på det som är viktigt. Borde inte svaret på den frågan vara bland den viktigaste dokumentation vi har? Det första du tänker på är kanske hur man gör sin kod tydlig, men borde inte dina tester beskriva det på ett ännu mer lättförståeligt, kort och koncist sätt? Hur skriver man tydliga tester?
dog_reading

Testnamnen borde dokumentera beteendet

Ett tips jag lärde mig från Sandro Mancuso för ett tag sen är göra namnen på både testklassen och metoderna läsbara precis enligt frågan ovan. Vad borde koden göra? A currency converter should... ACurrencyConverterShould. Vad borde koden göra? Return amount according to the conversion rate... return_amount_according_to_conversion_rate(). Sammantaget blir det en fullt läsbar specifikation:ACurrencyConverterShould.return_amount_according_to_conversion_rate()Om testet fallerar så är det ju tydligt vad både testet och koden borde göra, och förhoppningsvis lätt att hitta vilken som har fel. Om testet framöver skulle börja verifiera flera saker eller bli klottrigt blir det också tydligt att det gör för mycket, och du kan vad tanken med det en gång var. Det blir som en dokumentation över vad koden borde göra.

Testkoden borde återspegla testnamnet

Jobba gärna med "Arrange, Act, Assert", men jag och Sandro vill slå ett slag för att börja "bakifrån" i den cykeln. Börja med vad du vill ska hända. Ska koden returnera något? Ska den kasta ett Exception? Skicka något till en annan tjänst? Jobbar dig sedan bakåt med hur du vill att koden ska anropas och vilken data som krävs för det, så blir fokuset på ett enda resultat som också borde vara namnet på ditt test. Det gör också att du kan hålla dina tester kortare, mer fokuserade och förhoppningsvis mer lättlästa.

Jag tycker också att Fluent Assertions med till exempel AssertJ gör dina förväntningar tydligare och mer läsbara. Ta till exempel en verifiering av att precis rätt element returneras i en lista:

assertThat(result).containsExactly(expectedElements);

Du läser det från vänster till höger och det är tydligt vad det är du vill ska hända.

Försök även att utelämna andra detaljer som inte är viktiga för testet. Du kan till exempel att bryta ut objekt till konstanter som återanvänds baserat på deras egenskaper (AN_INVALID_USER) istället för att återupprepa skapandet eller dölja det i kryptiska hjälpmetoder.

Fokusera på resultatet du vill ha, och ignorera i största möjligaste mån hur koden tar sig dit. Föredra riktig kod och arbeta med alla typer av ersättare, inte bara mockar. Då får du robustare kod som inte går sönder så lätt när du försöker refaktorisera och med mindre sådan uppsättning i dina tester blir de också kortare och tydligare.

Beskriv beteendet innan du skriver koden

Sist, men inte minst - det bästa sättet jag vet att få tydliga tester är beskriva beteendet innan du skriver koden, vilket är precis vad du borde göra med TDD. Börja med att klura ut hur du vill att koden ska bete sig och beskriv det på ett tydligt och läsbart sett i ditt test. Sen implementerar du koden efter den tydliga strukturen. Då brukar både testerna och koden bli tydligare.