Enhetstestning i praktiken

Version 3.1
Jöns Weimarck, MSC 2008

Intro

 

Mina senaste konsultuppdrag har alla varit som deltagare i lättrörliga projekt där enhetstestning varit en viktig del. Jag började vid någon tidpunkt i dessa projekt att lite mer aktivt försöka att dels lära av andra, dels se vad som fungerade och vad som inte fungerade när jag själv skrev mina enhetstester. Resultatet har blivit de följande tipsen som förhoppningsvis kan vara bra att ha i bakhuvudet när man skriver enhetstester. De flesta tipsen bör gälla oberoende av programmeringsspråk, men de är skriva med Java och .Net i tankarna. Lite underförstått är också att man använder ett ramverk som xUnit eller liknande.
I texten försöker jag exemplifiera några av tipsen med små kodsnuttar. Som vanligt med exempel är de tryggt tillrättalagda för att författaren ska få fram sin ståndpunkt, men förhoppningsvis ska de ändå fungera som just exempel.

Innan jag börjar kastar jag också in brasklappen att det handlar om tips och inte några formella best practises. Det går att argumentera för att göra på andra sätt, men detta fungerar för mig.
Kort-kort om mockobjekt och test doubles
Jag tänkte inte skriva något mer ingående om det som om vart annat kallas mock objects, fakes, mockups, dummys eller något liknande. Däremot tänkte jag slå ett slag för en gemensam terminologi eftersom alla dessa olika namn kan vara ganska förvirrande. Jag har därför försökt följa Gerard Meszaros namngivning, vilket kortfattat innebär att samlingsnamnet för alla varianter av objekt som man använder som ersättare för ”riktiga” objekt vid tesning, är ”test double”. Ordet är en lek med ”body double” som är begreppet för skådespelare som får ersätta de riktiga skådespelarna när det är dags för stunt- eller nakenscener.
Vill man vara mer exakt finns det sedan olika varianter av test doubles beroende på hur de fungerar, mock object är till exempel en variant, men jag kommer hålla mig till det mer allmänna test double.



Så! Nu är det dags att börja!
1. Skriv kod och testkod samtidigt
Rubrikens uppmaning kommer nog inte som en nyhet för någon. Tvärtom är det något som mer eller mindre alltid förs fram i artiklar om testdriven utveckling och lättrörliga metoder. Men just detta, att det redan sagts många gånger, är faktiskt en intressant detalj. Om det är så bra, varför är det då så svårt att ens försöka? Min känsla är att man gärna överger hela idén utan att ens en gång prova.
Om jag ser till mig själv så tror jag att den stora utmaning varken är teknisk eller handlar om att jag måste göra saker i ny ordning. Jag tror det handlar om programmerarkynne (se där, ett nytt ord!).
Det jag tror är att många med mig upplever som glädjen i programmering är att man sysslar med kreativt nyskapande, man bygger upp något efter idéer man har i huvudet. När vi, eller i alla fall jag, tänker på testning så är det svårt att känna samma glädje eftersom föreställningen av test lätt handlar om uttömmande verifiering istället, vilket inte känns lika roligt.
Men håll i hatten nu, enhetstester har inte mycket gemensamt med den negativa föreställningen om test, att skriva enhetstester är i själva verket inte mycket skilt från att skriva ”vanlig” kod! Skriver man tester samtidigt som koden som testas, så är det ofta i testerna man är kreativ och kommer fram till hur koden ska se ut, koden som testas skriver man i de fallen mer mekaniskt. Förhoppningsvis blir detta lite tydligare i det tillrättalagda exemplet nedan.

Kanske kan jag med detta övertyga dig om att prova att skriva ett test samtidigt som koden som ska testas. Genast kommer då nästa fråga: ”Jaha, men hur börjar man då?”. Två småtips som kan vara till hjälp när man sitter med en tom skärm framför sig kan vara att dels börja med ett simpelt test för en så kallad ”happy path”, det vill säga att börja testa något simpelt där allt går som det ska, dels att skriva testet bakifrån. Vi ska se exempel på detta i vårt första kodexempel som går ut på att vi testa och skriva bilsimuleringsklassen Car. Vi kommer att börja skriva testfall redan innan vi har någon kod att testa och på detta sätt låta testerna driva kodutvecklingen.

När man skriver testfall och koden som testas samtidigt är ett vanligt trick att släppa lös sina multipla personligheter och låtsas vara dels en testare och dels en kodare. Testarens uppgift är att hela tiden skriva tester som visar hur dålig koden är, medan kodarens uppgift är att skriva så enkel kod som möjligt som gör att testarens tester kan köras felfritt.
Testaren i mig värmer därmed upp fingrarna lite och börjar sedan skriva på ett första test. Enligt ovan är det en bra idé att börja med ett väldigt enkelt scenario, så det får bli att testa att bilen rapporterar att den är startad efter att man har startat den. Ett så enkelt test kan jag antagligen skriva från början till slut med en gång, men om jag känner mig lite osäker på var jag ska börja så skriver jag alltså testet bakifrån istället, eftersom jag i alla fall förhoppningsvis vet vad det är jag ska testa:

public void testStartCar()
{  
   assertTrue(car.isStarted());
}

Har jag skrivit denna testrad så är det bara att börja nysta upp vad jag behöver skriva. Eftersom jag använder en car så måste jag skapa den, och att anropa start() för att starta bilen verkar rimligt. Det ena ger det andra... och simsalabim så har vi ett test innan vi skrivit någon kod att testa!

public void testStartCar()
{
   Car car = new Car();
   car.start();  
   assertTrue(car.isStarted());
}

Som testskrivare är jag nu nöjd eftersom jag gjort ett test som borde kunna köras felfritt, men inte gör det. Jag tar därför av mig testhatten och sätter på mig kodarkepsen. Nu gäller det att skriva enklaste koden som gör att testfallet körs felfritt.
Den här fulimplementationen av Car är tillräcklig för testfallet. Den returnerar alltid true i isStarted(), men det räcker för att testfallet ovan ska kunna köras utan fel.

public class Car
{
   public void start(){}

   public boolean isStarted()
   {
     return true;
   }
}

Jaha, så var det dags att ta av kodarkepsen och sätta på testhatten igen. Jag skriver nu ett nytt test som ska testa att bilen inte är startad om vi inte först startar den!
Detta test kommer att kräva att vi gör Car lite mer avancerad och vi driver på det sättet kodskrivningen genom att skriva tester.

public void testNotStartedCarIsNotStarted()
{
   Car car = new Car();
   // car.start();  
   assertFalse(car.isStarted());
}

På det här sättet kan man fortsätta ett tag att växla mellan kod- och testskrivande och konstruera en klass som garanterat fungerar enligt de krav man uttrycker i testerna. Exemplet är som sagt självklart tillrättalagt, men principen att skriva omväxlande kod och test håller faktiskt, och att skriva test bakifrån är också ofta bra!

Testerna ska sedan naturligtvis kompletteras efteråt varefter nya krav blir tydliga och dessutom utökas när man upptäcker buggar. Ett bra sätt vid buggrättning är att verifiera att buggen finns genom att skriva ett testfall som inte kan köras felfritt på grund av buggen. Man rättar därefter koden och vet att man är klar när testet kan köras felfritt.

2. Refaktorera när testet kan köras felfritt
Det finns ett problem med ordet ”enkel” i meningen ”kodarens uppgift är att skriva så enkel kod som möjligt” i tipset ovan. ”Enkel” i betydelsen ”första bästa lösning jag kan komma på”, kommer bara att fungera som slutgiltig lösning i mycket sällsynta fall, som i vårt simpla exempel. ”Riktig” kod ingår i ett större sammanhang och när vi väl har fått testet att köras felfritt är det därför dags att lyfta blicken lite och se om vi kan göra vår lösning ”enklare” med syftning inte bara på den kodsnutt vi precis skrivit utan på all annan berörd kod. Vi ska försöka skriva om vår lösning så att den totala koden blir så enkel som möjlig. ”enklare” i detta fall handlar alltså om kod som är mindre komplex, mer lättläst, lättare att underhålla.
Det kan kännas lite konstigt att skriva om lösningen precis när vi fått den att fungera, varför skriver vi inte den rätt från början? Svaret är att det är enklare att utgå från funktionellt fungerande kod och göra denna mindre komplex, än att utgå från icke fungerande kod (eftersom ett test inte kan köras felfritt) och göra denna kod funktionell samtidigt som vi ska ha koll på hela systemets grad av komplexitet. Vi gör en sak i taget helt enkelt.

Det är självklart inte alltid detta refaktoreringssteg innebär någon kodändring, man kanske tycker att koden och den befintliga lösningen är tillräckligt bra, det viktiga är att man tar ett genomtänkt beslut om detta!

3. Gör tillståndstester och interaktionstester
Tillståndstester och interaktionstester två olika typer av enhetstester. Tillståndstester motsvarar ”State Tests” på engelska och interaktionstester kallas, inte helt förvånande, för ”Interaction Tests”.
Den bakomliggande tanken med dessa två test är att man kan vara ganska säker på att ens kod fungerar som tänkt om man för varje enhet dels testar enhetens tillståndsförändringar, dels hur enheten anropar andra enheter.
I praktiken kan ett testfall innehålla båda typerna av test, men genom att hålla isär begreppen kommer I alla fall jag lättare ihåg att skriva testsatser för båda.
Tillståndstester inom enheten
Tillståndstester är helt vanliga enhetstester på enheten som ska testas. Vanligtvis skickar man in parametrar till en viss metod och kontrollerar sedan hur objektets tillstånd förändrats genom returvärdet på anropet, eller kanske genom att anropa någon getter-metoder. Tillståndstester är blackbox-teser i den meningen att testerna så lite som möjligt ska vara beroende av klassmetodernas implementation. I princip anropar ett värdetest en metod och verifierar sedan objektets tillstånd, hur tillståndet har kommit till är ointressant för testet.
Interaktionstester mellan enheter
I ett interaktionstest verifierar man hur enheten kommunicerar med andra enheter. Det kan naturligtvis göras på olika sätt och mer eller mindre detaljerat, jag brukar nöja med att kontrollera att rätt metod på den externa enheten anropats med rätt parametrar. Jag är inte lika intresserad av vad den externa enheten returnerar, det testas i den enhetens tester.
Dessa interaktionstester kan tyckas onödiga, räcker det inte att tillståndstesta varje enhet? Jo, ofta gör det det, men tillståndstesterna visar bara att enheten fungerar om den blir anropad med de parametrar vi använder i tillståndstesterna, och vi vet inte om det är vad som kommer att ske när koden används av andra enheter. Med interaktionstester får vi en kontroll på att enheterna anropar varandra och att de anropar på rätt sätt, en enkel form av integrationstest om man så vill.
Till skillnad från tillståndstester är interaktionstester dessvärre så kallade whitebox-tester. När vi vill kontrollera att metod a i klass A anropar metod b i klass B med rätt parametrar är det ju faktiskt implementationen av a vi testar, vilket inte egentligen är vårt mål. Vi vill testa vad som görs (att metoden vi testar utför rätt logik), inte hur det görs.

Jag tycker alltså ofta ändå det är motiverat att göra dessa tester av kommunikationen mellan vår enhet under test och andra enheter, trots att det vi sysslar ska vara ”enhetstestning” och inte ”integrationstestning”. Jag skriver ”andra enheter” och inte ”andra klasser” för att markera att enheten mycket väl kan vara större än en klass. Och om enheten till exempel är en hel objektstruktur så ska inga interaktionstester göras mellan de ingående objekten.

Här följer ett litet exempel. Ett tillståndstest är den vanligaste sortens enhetstest, man anropar en metod och kontrollerar hur objektets tillstånd ändrats. Vårt simpla bil-testfall duger därmed utmärkt:

public void testStartCar()
{
   Car car = new Car();
   car.start();  
   assertTrue(car.isStarted());
}

Om vi i stället ska skriva ett interaktionstest för Car kan vi först anta att Car blivit lite mer komplicerad och nu använder en Engine. En interaktionstest är egentligen svårmotiverad här, Car och Engine är nog en enhet och ska testas tillsammans, men för att visa tekniken gör vi ett undantag. Vi måste också för exemplets skull anta att car.start() i sin tur anropar engine.start() när man vill starta bilen. Det vi vill testa är alltså att just detta händer: Ett anrop till car.start() ska medföra att engine.start() blir anropad.
Ett typiskt sätt att testa detta är att ersätta den riktiga Engine med en testversion, en test double, som helt enkelt bara håller reda på om den blivit anropad eller inte.
Ett enkelt test skulle då kunna se ut så här:

public void testStartCarCallsEngine()
{
    Car car = new Car();
    car.setEngine(new EngineDouble());
    car.start();
    assertTrue(engineDouble.startWasCalled);
}
4. Testa bara det som ska testas
Fall inte för frestelsen att testa flera olika saker i samma testfall bara för att det går!  Med olika saker menar jag inte att det bara ska finnas en testsats i varje testfall, men de olika testsatserna ska logiskt hänga samman. Det kan så klart ibland finnas lite flytande gränser, men ett test som innehåller testsatser både för att verifiera att ett fönster har rätt storlek och att fönstret ritat ut rätt knappar tycker jag är ok. Det kan logiskt sett ses som en verifiering av fönstrets grundläggande GUI. Men om man till dessa testsatser även lägger till en testsats för att verifiera vad som händer när man trycker på en viss knapp, då har man gått för långt.

 Lockelsen att testa flera olika saker i samma test har två grundorsaker:

1. Lathet (en annars i kodsammanhang inte alltid föraktfull egenskap): ”Om jag testar både a och b i samma testfall behöver jag bara skriva ett testfall istället för två.”
2. Övertestning: ”Det ökar säkerheten att b fungerar om jag testar både a och b i samma testfall fast jag vet att b redan testas i ett annat testfall. Kostnaden är ju bara en extra rad kod!”

Att resonera som ovan är tyvärr inget som håller i längden. När kodbasen växer kommer testfall som testar flera olika saker garanterat att vara förvillande och ta onödig tid att underhålla. Tiden man tyckte sig tjäna genom att bara skriva ett testfall kommer man garanterat att förlora mångfalt. På samma sätt lurar man sig med övertestning. Om man ändrar i koden för b vill man såklart inte behöva ändra i onödigt många tester. Och att i efterhand behöva reda ut vilket testfall som ”egentligen” ska testa b tar onödigt mycket tid.

Lösningen vid övertestning är helt enkelt att låta bli att övertesta! Om man i stället lider av lathetssyndromet och har bakat in flera testfall i ett, så är man så illa tvungen att dela upp testfallet i flera. Som vanligt gäller gamla goda programmeringsregler där koddelar som upprepar sig i de två testfallen ska brytas ut!

5. Låt konstruktorn vara ofarlig
En klass som gör mycket i sina konstruktorer blir ofta onödigt svårtestad, eftersom testet då måste ta hänsyn till det maskineri som startas oberoende om det är väsentligt för testet eller inte.
Klassen blir också svårare att använda i de fall när den inte själv är under test utan bara är en hjälpklass till en annan klass som testas. Så fort klassen som testas initierar sin hjälpklass måste man i så fall ta hänsyn till vad som sker, trots att testklassen kanske inte ens kommer att använda hjälpklassen i det aktuella testfallet.
 
I testsammanhang vill vi helt enkelt så lätt som möjligt kunna skapa de objekt vi behöver och om möjligt slippa behöva bry oss om eventuella beroenden om de inte är väsentliga för det som ska testas. Ett bra sätt att uppnå detta på är självklart att försöka hålla konstruktorerna så simpla som möjlig, i princip ska konstruktorerna bara användas till att initiera medlemsvariabler. Att i konstruktorern göra annat ska man se upp med och fundera på om det inte är något som kan göras i en egen metod istället och utföras när, och om, det verkligen behövs.
Följande är ett exempel på en klass som blir onödigt svårtestad på grund av sin konstruktor som vill läsa in en konfigurationsfil från filsystemet. I ett test vill vi absolut inte vara beroende av att vissa filer ligger på rätt ställen.

public class Car
{
   EngineData engineData;

   public Car(String configFilename)
   {
      engineData = readConfigFromFile(configFilename);
   }

   public EngineData getEngineData()
   {
 return engineData;
   }
}

Exemplet nedan är mycket bättre, nu kan vi skapa ett objekt utan att bekymra oss om någon fil.

public class Car
{  
   public Car()
   {
   }

   public EngineData getEngineData(String configFilename)
   {
 return readConfigFromFile(configFilename);
   }
}

Att inte skapa beroenden i konstruktorn hänger delvis ihop med rubriken nedan.
6. Möjliggör injicering av beroenden
En klass som är beroende av andra klasser och själv initierar dessa objekt, blir lätt svårtestad i de fall man vill använda test doubles istället för de riktiga objekten. Även en metod kan bli onödigt svårtestad om den själv skapar nya objekt som den sedan anropar. Lösnigen i båda fallen är att injicera beroenden (dependency injection)
Injicera beroenden i klasser
Vi återvänder till vår Car som vi låter ha en privat medlem av typen Engine som den skapar i sin konstruktor och sedan använder i olika metoder. Med tanke på föregående rubrik låter vi initieringen av Engine vara bekymmerslös, det ställs inga krav på oss när en Engine automatiskt skapas när vi skapar en Car. Problemet är här istället att om man nu ska testa Car måste vi hela tiden ta hänsyn också till hur klassen Engine fungerar eftersom Car använder Engine. Ofta är det det man vill, Engine kan testas samtidigt med Car, men ibland vill man testa Car isolerat. Man skulle då vilja använda en test double-variant av Engine i stället, men det är svårt eftersom det inte finns något sätt att ersätta Engine med sin test double på. Det är det problemet som löses med injicering av beroende.

Det finns två varianter av beroendeinjicering, ofta kallade constructor dependency injection (cdi) och setter dependency injection (sdi). cdi innebär att man till konstruktorn skickar med alla objekt som klassen är beroende av, man kan då enkelt kasta med en test double istället för en av dessa beroenden. sdi innebär att man istället tillhandahåller setter-metoder för objekt som klassen är beroende av. Utan att gå in för mycket på detaljer så kan man argumentera till för- och nackdel för båda sätten, och vilken som är bäst kan naturligvis också variera från fall till fall. Ofta uppstår tyvärr en blandning av de båda, där man i slutändan tvingas skicka med vissa objekt in i konstruktorn medan andra beroenden ska injiceras genom settermetoder. En sådan blandning är förstås bara förvillande!

Själv har jag märkt att jag oftare och oftare väljer cdi. Den främsta fördelen är att objekt som konstrueras med cdi alltid är kompletta, inga setter-metoder måste anropas för att objektet ska bli funktionsdugligt. Nackdelen är främst att man måste uppdatera konstruktorsignaturen ofta under utvecklingens gång när nya beroenden växer fram, med mycket kod och mycket tester som anropar konstruktorn kan det bli en hel del omskrivningar.

Nedan följer en implementation av Car som kan vara onödigt svår att testa. Så fort vi skapar en Car kommer vi också att skapa en Engine, och vi har ingen möjlighet att byta ut denna mot en test double om vi skulle vilja det!

public class Car
{
   private Engine engine;
 
   public void Car()
   {
      engine = new Engine();
   }
}

Nu blir det mer testbart med cdi! Vi skickar med Engine när vi skapar Car och kan därmed skicka med en test double i stället.

public class Car
{
   private Engine engine;

   public void Car(Engine engine)
   {
      this.engine = engine;
   }
}


...Och så varianten med sdi:

public class Car
{
   private Engine engine;
 
   public void Car()
   {
   }

   public void setEngine(Engine newEngine)
   {
      engine = newEngine;
   }
}


Till sist en variant av sdi där vi skapar en Engine i konstruktorn men också tillhandahåller en setter-metod om vi vill ändra denna Engine till en annan implementation. Innan man använder denna sista variant ska man vara medveten om den extra komplexitet den tillför. Är det värt besväret?

public class Car
{
   private Engine engine;
 
   public void Car()
   {
      engine = new Engine();
   }

   public void setEngine(Engine newEngine)
   {
      engine = newEngine;
   }
}
Injicera beroenden i metoder
Metoder som lokalt initierar och använder hjälpklasser kan ställa till samma problem som de ovan. Vill man i ett test att metoden ska använda en test double istället, måste man kunna injicera detta beroende. Vid test kan man då skicka in sin test double istället för det riktiga objektet.

Istället för att göra så här:

public void calculateAirResistance()
{
   AirCalculator ac = new AirCalculator ();
   ac.calculateAirResistance(this);
   …
}

…Kan man ofta göra så här:

public void calculateAirResistance (AirCalculator)
{
   ac.calculateAirResistance(this);
   …
}

Beroenden som på detta sätt injiceras på metodnivå och inte sparas ger mer fristående och självständiga klasser än klasser som sparar sina beroenden som medlemsvariabler. En bra allmän strategi kan därför vara att till en början injicera beroenden direkt i de metoder som behöver dem. Om det sedan visar sig att samma beroende kanske injiceras i allt för många metoder, då först är det dags att överväga sdi eller cdi.
7. Förändra inte enheten som testas
Det kan ibland vara lockande att ärva av klassen som ska testas och skriva testet mot denna subklass istället för den riktiga klassen. Anledningen kan vara att man vill förändra klassen för testbarhetens skull, till exempel göra en ny implementation av en metod som kommer att anropas så att metoden alltid returnerar ett bestämt värde. Uppsåtet med att skriva testet mot en subklass är alltså gott; man vill göra ändringar i klassen som gör testfallen enklare att skriva.
Tyvärr är det oftast ingen bra idé. Ska man testa en viss klass, så är det naturligtvis den klassen man ska skriva testerna mot, inte mot någon annan. Ett test mot en subklass skapar osäkerhet: testas verkligen den riktiga klassen? Testet riskerar också att bli onödigt tidsödande att underhålla då det kan vara svårare för någon annan att se vad som egentligen försiggår.
Så hur ska man göra då? Viljan att utnyttja arv tyder på att klassen inte är tillräckligt testvänlig, och boten mot det är ofta att bryta upp klassen i flera klasser. Förhoppningsvis kan då till exempel den metod man vill ändra på placeras i en hjälpklass till klassen som testas. Denna hjälpklass kan man sedan låta ersätta med en test double, och på det sättet få samma fördel som med arv, men utan nackdelarna.

Ett litet exempel kan se ut så här. Vi tittar återigen på vår Car som nu har nu utrustats med en metod som är tänkt att anropas regelbundet för att simulera att bilen går sönder med slumpmässiga intervall (ja, det är ett ganska krystat exempel). En gång av hundra kommer metoden att returnera true vilket ska tolkas som att bilen nu är sönder.

public boolean randomFailureHappened()
{
   int random = getRandom(1,100);
 
   if (random == 2)
   {
      return true;
   }
  
   return false;
}

Problemet uppstår när vi vill testa en metod som i sin tur anropar randomFailureHappened(). Eftersom metoden vi testar kommer att få olika svar från randomFailureHappened(), kommer vårt test också att bli beroende av slumpmässigheten i metoden och det är verkligen inte vad vi vill!  Det är nu det kan vara lockande att göra testet mot en subklass istället, där vi kan ändra på implementationen av randomFailureHappened() så att den till exempel alltid returnerar false;
Tipset är att istället göra något liknade det nedan. Vi bryter ut själva slumphanteringen till en egen klass som vi sedan anropar. Vi kan då ersätta denna klass med en test double som vi till exempel kan låta returnera ”Failure.NoFailure” alltid.

public boolean randomFailureHappened()
{
   failure = failureManager.getRandomFailure();
   if (failure == Failure.NoFailure)
   {
      return false;
   }
   return true;
}
8. Testbarhet får synas
Om enhetstestning utgör en del av projektmetoden så ska det rimligtvis också få märkas på koden! Det är med andra ord inte otillåtet eller ens fult att låta kodens utseende styras av testbarheten. I de mest allmänna fallen innebär det inte mer än vad som föreslås under ”Låt konstruktorn vara ofarlig” och ”Möjliggör injicering av beroenden”. Detta brukar ingen ha svårt att acceptera, men ibland kan det också vara acceptabelt med metoder som enbart är till för att underlätta testning, i synnerhet om det handlar om att försöka testa kod som är skriven utan testbarhet i åtanke. Naturligtvis ska inte dessa metoder skrivas för att undvika ansträngningen att refaktorera en svårtestad klass, men efter att ärligt ha övervägt alternativen att refaktorera klassen eller att testa på något annat sätt, kan man ibland komma till slutsatsen att det helt enkelt är bättre att göra en testspecifik metod än att försöka slå knut på sig själv, och koden, bara för att undvika det. Man kan också argumentera för att om koden utvecklas testdrivet så kommer den naturligt att kunna innehålla metoder som bara används vid testning eftersom det är på det sättet testdriven utveckling går till. Ett exempel är metoden isStarted() i klassen Car i exempelkoden till rubriken ”Skriv kod och tester samtidigt”.

 Självklart gäller inte resonemanget ovan om det är ett publikt API man skapar där metoderna ska användas av en tredjepart, då måste de publika metoderna fylla en annan funktion än att bara öka testbarheten

Ett exempel på hur testbarhet inte får synas är när man bakar in testspecifikkod i befintliga metoder och får kod som ser ut så här:

if (! underTest )
{
   doThis();
}
else
{
   doThat();
}

Som avslutning kan det vara motivert att återigen betona att testspecifika metoder inte är något medel för att undvika refaktorering av svårtestade klasser.
9. Testa inte extern kod
Man måste kunna lita på att extern kod man använder faktiskt fungerar, och man ska därför inte behöva testa den. Om man ska testa vad som händer när man trycker på en knapp skapad i ett GUI-ramverk, så ska man alltså inte försöka simulera själva knapptryckningen för att kontrollera att metoden som ska anropas verkligen blir anropad. Det måste man lita på att ramverkskonstruktörerna har testat! I stället ska man koncentrera sig på vad som händer när knappmetoden blivit anropad, det vill säga att koden man själv skrivit är rätt. Om GUI-ramverket kräver att man på något sätt kopplar knappmetoden till knappen så bör man också testa att denna koppling görs.
10. Lär av andras tester
Vi är vana att läsa andras kod när vi jobbar. Mer eller mindre omedvetet inspireras vi av bra lösningar och förhoppningsvis snappar vi upp saker som vi senare använder själva. Det är en naturlig del av lärandet inom systemutveckling, och det är väl i någon mån detta som ledit vidare till att man kommit att systematiskt samla ihop återkommande bra lösningar och formaliserat dem som designmönster.
Tyvärr går det inte till likadant vid enhetstest, vi läser helt enkelt vanligtvis inte andras enhetstester på samma sätt som vi läser andras kod.
Ett exempel på detta är när vi ska rätta ett test som slutat köra felfritt efter att man gjort ändringar i koden som testas. Vi koncentrerar oss då på att på något sätt göra en precisionsinsats i testkoden och, gärna utan att över huvudtaget försöka förstå testet, ändra testraden som fallerar. Jämför det med hur man gör när man upptäcker en bugg i körande kod. Även om man kan hitta raden där felet uppstår så nöjer man sig ofta inte med att bara ändra denna. Man försöker först, i alla fall lite grand, sätta sig in i den omgivande koden. Kanske ser man också att det finns ett mycket bättre sätt att utföra det koden är till för och gör en refaktorering istället för att bara ändra på buggraden.

Jag vill alltså slå ett slag för att vi ska börja titta lite mer på andras testkod och och lära av denna, på samma sätt som vi gör med ”vanlig kod”.
11. Använd tester som kodexempel
Många tester kan fylla en funktion utöver det självklara att testa kod. De kan också fungera som kortfattade exempel på hur koden kan användas, och därmed vara en viktig del av koddokumentationen.
I exemplet med vår Car skulle en ny programmerare som undrar hur man använder klassen kunna se i ett test att för att starta bilen så ska man först skapa Car med den argumentlösa konstruktorn, sedan anropa init() och till sist start(). Han kan vara helt säker på att detta fungerar eftersom enhetstesterna fungerar!

Försök alltså skriva testerna så att de även kan användas i detta syfte när det är relevant. Det innebär till exempel att försöka tänka ut lika bra namn på variabler och metoder i testfallen som man gör i den vanliga koden. Och man ska även i övrigt följa vanliga objektorienteringsregler för att göra koden läsbar och lätt att förstå.
 
För mig själv har detta inneburit att jag ändrat mitt sätt att skriva enhetstesterna. Innan tyckte jag att det var ok att i enhetstesterna i detalj visa varje steg jag gjorde för att skapa objekt och sätta dem i rätt tillstånd. Nu bryter jag mycket oftare ut kod till egna metoder och försöker ge dem beskrivande namn. Jag föreställer mig att det då för en framtida läsare, eller för mig själv tre dagar senare, både är lättare att på en hög nivå se hur testfallets logik hänger ihop, och samtidigt lättare att gå till en utbruten metod för att få se kodexempel på detaljnivå. Men egentligen är ju detta inget annat än vanlig god programmeringssed.