Enhetstestning , ibland enhetstestning eller enhetstestning ( eng. unit testing ) är en process inom programmering som låter dig kontrollera att enskilda moduler i programmets källkod är korrekta , uppsättningar av en eller flera programmoduler, tillsammans med motsvarande styrdata, användnings- och bearbetningsprocedurer.
Tanken är att skriva tester för varje icke-trivial funktion eller metod. Detta gör att du snabbt kan kontrollera om nästa ändring i koden har lett till regression , det vill säga till uppkomsten av fel på de redan testade platserna i programmet, och underlättar också upptäckten och elimineringen av sådana fel. Du kan till exempel uppdatera biblioteket som används i projektet till den aktuella versionen när som helst genom att köra tester och identifiera inkompatibiliteter.
Målet med enhetstestning är att isolera enskilda delar av ett program och visa att de delarna fungerar individuellt.
Denna typ av testning görs vanligtvis av programmerare .
Enhetstestning gör det senare för programmerare att refaktorera samtidigt som de är säkra på att enheten fortfarande fungerar korrekt ( regressionstestning ). Detta uppmuntrar programmerare att ändra koden, eftersom det är lätt nog att kontrollera att koden fortfarande fungerar efter ändringen.
Enhetstestning hjälper till att eliminera tvivel om enskilda moduler och kan användas för en nedifrån-och-upp-metod för testning: först testa enskilda delar av programmet och sedan programmet som helhet.
Enhetstest kan ses som ett "levande dokument" för klassen som testas . Klienter som inte vet hur man använder denna klass kan använda enhetstestet som exempel.
Eftersom vissa klasser kan använda andra klasser, sträcker sig testning av en enskild klass ofta till relaterade klasser. Till exempel använder en klass en databas; medan han skriver ett test upptäcker programmeraren att testet måste interagera med databasen. Detta är ett fel eftersom testet inte får gå utanför klassgränsen. Som ett resultat abstraherar utvecklaren bort databasanslutningen och implementerar detta gränssnitt med sitt eget mock-objekt . Detta resulterar i mindre sammanhängande kod, vilket minimerar beroenden i systemet.
Mjukvarutestning är en kombinatorisk uppgift. Till exempel skulle varje möjligt värde på en boolesk variabel kräva två tester, ett för TRUE och ett för FALSE. Som ett resultat kommer varje rad med källkod att kräva 3-5 rader testkod.
Algoritmer som Marching-kuber eller röd-svart träd har ett grenat beslutsträd, och enorma testsviter behövs för att kontrollera alla alternativ: i en av de röd-svarta trädimplementeringarna från GitHub gjordes tolv tester för att kontrollera insättning [1] . I den andra bygger de automatiskt 10! = 3,6 miljoner permutationer och upplev dem alla [2] .
Liksom all testteknik tillåter enhetstestning dig inte att fånga alla programfel. Detta följer faktiskt av den praktiska omöjligheten att spåra alla möjliga vägar för programexekvering, utom i de enklaste fallen.
Till exempel inom matematisk modellering . Affärsapplikationer fungerar ofta med ändliga och räknebara uppsättningar, medan vetenskapliga applikationer fungerar med kontinuerliga . [3] Därför är det svårt att välja tester för var och en av programgrenarna, det är svårt att säga om resultatet är korrekt, om noggrannheten bibehålls, etc. Och i många fall bestäms kvaliteten på modelleringen "med ögonen" ", och det sista resultatet registreras som "referens". Om en avvikelse hittas kontrolleras det nya resultatet manuellt och det avgörs vilket som är bättre: det gamla eller det nya.
Kod som interagerar med portar , timers , användare och andra "instabila" delar av systemet är extremt svår att testa i en isolerad miljö.
Men detta betyder inte att enhetstestning är helt olämplig här: det tvingar programmeraren att flytta från filer och portar, till exempel, till abstrakta strömmar . Detta gör koden mer allmän (till exempel kan du byta från filer till nätverksuttag utan problem ), mer testbar (du kan kontrollera situationen med "förlorad anslutning" genom att skriva en ström som, efter att ha utfärdat N byte, kommer att simulera en olycka; kontrollera under Windows-delen av Unix -vägkonverteringsfunktionerna
Det är i grunden en instabil del av systemet. Dessutom är enhetstester vanligtvis enkla, medan tester för flertrådade system tvärtom borde vara ganska stora.
När man utför enhetstester testas var och en av modulerna separat. Detta innebär att integrationsfel, systemnivåfel, funktioner som körs i flera moduler inte kommer att upptäckas. Dessutom är denna teknik värdelös för prestandatester. Således är enhetstestning effektivare när den används i kombination med andra testtekniker.
För att skörda fördelarna med enhetstestning krävs strikt efterlevnad av testteknik under hela mjukvaruutvecklingsprocessen. Det är nödvändigt att inte bara föra register över alla tester som utförts, utan också över alla ändringar av källkoden i alla moduler. För detta ändamål bör ett programversionskontrollsystem användas . Således, om en senare version av programvaran misslyckas med ett test som har godkänts tidigare, kommer det att vara lätt att kontrollera varianterna av källkoden och åtgärda felet. Du måste också se till att underkända test spåras och analyseras hela tiden. Att ignorera detta krav kommer att leda till en lavin av misslyckade testresultat.
Förutom i de enklaste fallen måste objektet som testas interagera med andra objekt. Dessa "kollaboratörer" - stubbobjekt - görs extremt enkla: antingen extremt förenklade (minne istället för en databas), eller designade för ett specifikt test och mekaniskt repeterande av utbytessessionen. Problem kan uppstå vid ändring av utbytesprotokollet, i vilket fall stubbobjekten måste uppfylla de nya protokollkraven. [fyra]
Det är enkelt att verifiera att modulen fungerar på utvecklarens maskin. Svårare - det på målmaskinen, ofta mycket begränsad [5] .
Extrem programmering förutsätter som ett av postulaten användningen av automatiska enhetstestverktyg. Denna verktygslåda kan skapas antingen av en tredje part (som Boost.Test) eller av applikationens utvecklingsteam.
Extrem programmering använder enhetstester för testdriven utveckling . För att göra detta skriver utvecklaren, innan han skriver koden, ett test som återspeglar kraven för modulen. Uppenbarligen bör testet innan du skriver koden inte fungera. Den ytterligare processen reduceras till att skriva den kortaste koden som uppfyller detta test. Efter att utvecklaren skrivit nästa test, kod och så vidare många gånger.
Komplexiteten i att skriva enhetstester beror på hur koden är organiserad. Stark sammanhållning eller ett stort ansvarsområde för enskilda enheter (klasser för objektorienterade språk) kan göra testning svårt. Stubbar bör skapas för objekt som kommunicerar med omvärlden (nätverk, fil I/O, etc.). I terminologi särskiljs mer "avancerade" stubbar - skenobjekt som bär logik. Det är också lättare att testa genom att dela upp så mycket av logiken som möjligt i rena funktioner . De interagerar inte med omvärlden på något sätt och deras resultat beror bara på ingångsparametrarna.
Det är vanligt att dela upp testkoden i separata kataloger. Det är önskvärt att det inte är en svår uppgift att lägga till nya tester i projektet och att det är möjligt att köra alla tester. Vissa versionskontrollsystem, som git, support hooks ( English hook ), med vilka du kan konfigurera lanseringen av alla tester innan du gör ändringar. Om minst ett av testerna misslyckas kommer ändringarna inte att genomföras. Kontinuerliga integrationssystem kan också användas .
Det finns enhetstestverktyg och bibliotek för de flesta populära högnivåprogrammeringsspråken. Några av dem:
Vissa språk har stöd för enhetstestning på syntaxnivå. Detta eliminerar behovet av att välja vilket ramverk som ska länkas till och gör det lättare att porta kod till andra projekt.
Ett exempel på sådana språk:
Kodexempel i D- språk
klass ABC { this () { val = 2 ; } privat int val ; public func () { val *= 2 ; } } unittest { ABC a ; a . func (); hävda ( a . val > 0 && a . val < 555 ); // du kan komma åt en privat variabel inuti modulen }