Sociala enhetstester med stubbar och spioner
Vi kanske ska börja från början.
Varför enhetstestar vi egentligen, och hur vet man om man har bra enhetstester eller inte?
Personligen tycker jag att bra enhetstester får dig och ditt team att känna att ni förstått hur koden ska fungera, att den gör vad ni tror, och att ni vågar ändra i koden utan att vara rädda för att förstöra nåt. Om man inte ändrar på något som tas in, returneras eller skickas vidare ut ur din kod men ändå får en hel drös med röda tester så blir man ju lite tveksam. Ändrar man något av tidigare nämnda men testerna fortfarande är gröna känns det ju inte mycket bättre. Helst vill man ju att testerna tydligt pekar ut precis det man ändrat.
Om man har tester som inte känns så bra, vad skulle det kunna bero på?
Om koden i testfallen är trasslig och krånglig säger det sig nog själv, men om den inte verkar så värst trasslig då...?
Det är ganska ofta man ser testkod där man vill returnera ett visst värde när en metod anropas, så man slänger in en expect() via sitt favoritramverk för mockning. Pfft, så gör ju alla säger du, men mockning är roten till massor av mörk testmagi och ondska! Om den där metoden inte anropas, spelar det egentligen någon roll för ditt test? Inte? Testet kommer gå rött ändå. Sätter du upp liknande beteende i flera tester, för att delar kanske inte ska bete sig på samma sätt? Då blir det massor att ändra på när du ändrar vilka anrop som görs. Följer du regeln ett test till en klass? Du får du dessutom en massa lager med mockning, som ger en hel kedja med ändringar när du ändrar i koden. Ser du vart jag är påväg? Mockning är smidigt, men det leder till så många tråkiga saker.
En annan tråkig kategori av tester är de evigt gröna testerna. Såna brukar man träffa på där man jagar testtäckningsprocent. Det är ofta tester utan assertions som bara kollar att koden kunde köras. Det är ju bra tänker du? Problemet är att du kan ha massor med kodtäckning utan att testa speciellt mycket alls, och på köpet gör du det rätt jobbigt att refaktorisera koden - massor av tester går ju sönder när du flyttar om. En annan sida av samma mynt är mocktestet - om ditt test bara testar att mocken returnerar rätt sak, testar du ditt system då eller mocken? Vad vinner du på det? Vill du vara säker på att du inte har evigt gröna tester kan du kika på mutationstestning, t.ex. med PI Test.
Den sista kategorin jag tänkte ta upp nu är den grötiga uppsättningen. Sätter du upp halva världen innan testerna, gärna i kombination med att de flesta testerna inte använder det mesta av den, så kan du ge dig sjutton på att du snart får tester som blir röda fastän du inte ändrar nåt i dem. Det finns helt enkelt ett dolt beroende på uppsättningen i vissa tester, och det är nästan alltid när man som minst vill se dem som de kryper fram och ställer till med bråk.
Vad kan man göra för att ge sina stackars tester lite mer kärlek?
Istället för mockar, jobba med stubbar och spioner! Precis samma tankesätt fast det ger kortare tester, bättre återanvändning, tvingar dig designa din kod för testning och utbytbarhet och gör dem mindre ömtåliga. Inte så pjåkigt va? Så gör dessutom Uncle Bob, Kent Beck och Martin Fowler. Man kan också fundera över att stubba precis utanför sitt system ibland, t.ex. med Hoverfly, för att få med saker som nätverksanrop och JSON-konvertering i sina enhetstester.
Se till att göra dina enhetstester sociala, alltså att de testar med så mycket riktig kod det går utan att göra dina tester långsamma eller ömtåliga och stubba/spionera resten. Börja med ett test och en klass och låt sedan klassen utökas, refaktoriseras och brytas ut i flera klasser medan du lägger till flera tester i din testklass. När uppsättningen för testerna börjar skilja sig, bryt isär dem också men låt inte kod och tester ha samma struktur - det gör det bara svårt att refaktorisera. Testerna ska ju fortfarande vara gröna när du refaktoriserar och det är svårt att göra om de testar klassen du just tagit bort, eller hur? Testa in- och utdata och sidoeffekter, det jag skulle kalla beteende. Det borde också leda till att du täcker det mesta av din kod - om du inte gör det kanske du hamnat i fällan att du har kod du inte behöver än (YAGNI)?
Andra saker man kan kika på är namngivning, struktur och att bara dölja de saker som inte är viktiga för testet. Använd gärna Fluent Builders för att sätta upp det du vill testa, med metoder som beskriver vad som är viktigt för testet och default-värden i övrigt. Försök att hålla dig till att göra en sak och sedan verifiera resultatet. Kika gärna på Fluent Builders även för assertions, t.ex. med AssertJ eller Truth. Given/When/Then är ett bra mönster som gör att folk känner igen sig i dina tester. Försök att göra namngivningen tydlig på dina tester också, för är den tydlig och du testar rätt saker så blir den som en dokumentation över hur ditt system är tänkt att fungera.
Du borde inte heller vara rädd att ta bort tester då och då, eller i alla fall skriva om och slå ihop dem. Om beteendet ändras, så borde testerna också göra det - det skulle ju fundera som dokumentation, eller hur?
Att testa ska vara lätt - tar det mycket längre tid att fixa testerna än att ändra koden, då är det förmodligen fel. Hoppas att du fått lite nya tankar och idéer för att ta dig ut ur trassliga, trasiga testträsket. Och kom ihåg - mockar är roten till mycket ondska!