Denne artikel omhandler emnet unit test. Årsagen til at jeg har skrevet den, er at jeg selv savnede noget information om hvordan og hvornår jeg skulle lave unit tests. Artiklen tager udgangspunkt i en række andre artikler samt nogle personlige erfaringer. Artiklen er bygget ud fra de konklusioner som jeg selv har udledt ved at læse om emnet, og er ikke nødvendigvis 100% korrekt på alle punkter. Hvis du som læser ser nogle forkerte antagelser eller beskrivelser er du velkommen til at kontakte mig enten på mail slamidtfyn@gmail.com∞, via twitter http://twitter.com/slamidtfyn∞ eller skrive en kommentar nederst i artiklen.
Alle eksempler er lavet i VS2008 med VB.NET og ved brug af VS indbyggede test framework. Eksemplerne er lavet som "quick n' dirty" løsninger for ikke at skulle lave alt for komplekse eksempler.
For at man kan teste sin kode, er der en vigtig faktor der skal tages hensyn til. Koden skal være testbar! - dvs at man som minimum bør adskille sin kode, således at forretningslogik er adskilt fra koden der driver brugerfladen. Hvis forretningslogikken er indlejret i button_click() funktioner osv er det meget svært, hvis ikke umuligt, at lave unit test. Læs mere om forretningslogik her: WikiPedia:Business_logic_layer∞. Men dette er ikke helt nok, nok kan man nu lave test af klasser og metoder men for at hjælpe sig selv, er der nogle flere ting at tage hensyn til. Hvis klasser og metoder skal være nemme at teste, kan man med fordel følge principperne for DependencyInjection (WikiPedia:Dependency_injection∞). Kort fortalt går det ud på, at sørge for at man ikke opretter en masse objekter, som en del af en klasse konstruktor.
Prøv for eksempel at se denne klasse:
Product.vb
Public Class Product PublicSubNew() Me.Wheels.Add(New ProductDetail()With{.Name = "Hjul"}) Me.Bolts.Add(New ProductDetail()With{.Name = "Bolt"}) EndSub
... End Class
Her bliver der som en del af konstruktoren oprettet nogle data, fordi på det tidspunkt hvor klassen blev lavet, var det en forudsætning at klassen skulle have nøjagtigt disse data. Hvis man skulle teste oprettelsen af denne klasse, vil man skulle tage hensyn til at netop disse variable blev oprettet. Hvis denne klasse skulle testes ville man fx lave følgende test:
ProductTest.vb
<TestMethod()> _ PublicSub ProductConstructorWheelsTest() Dim target As Product = New Product
Assert.IsTrue(target.Wheels.Count = 1) EndSub
<TestMethod()> _ PublicSub ProductConstructorBoltsTest() Dim target As Product = New Product
Assert.IsTrue(target.Bolts.Count = 1) EndSub
Men denne test er meget sårbar, hvis fx der pludselig ændres i krav, så produktet skal have 2 hjul og dette laves som en del af konstruktoren vil testen fejle. Derfor vil en bedre løsning være at lave en factory klasse der opretter de korrekte data til produktet, således at produktet kan testes uafhængigt af dette. Dette er et utroligt simpelt eksempel, der kunne have været en række andre objekter der blev oprettet, som gav disse problemer fx en reference til en datakilde eller en logfil, som man ikke kan eller vil teste.
For at man får den rigtige mening med unit tests, bør man følge princippet omkring Test Driven Development (WikiPedia:Test-driven_development∞). Her er der lagt vægt på at man bør skrive sin test, inden man skriver den funktionalitet der skal testes. Det kan dog være en smule besværligt at gøre dette i VS2008, da man herved lidt mister muligheden for at oprette test klasser automatisk ud fra sin forretningslogik. Jeg bruger selv følgende metode til at omgå dette.
Opret klasse med kun en konstruktor og ikke andet
Opret test klasse ved hjælp af funktionerne i VS
Skriv test og tilføj eventuelle parametre og properties til klassen og se testen fejle
Skriv kode og se testen gå godt
Opret eventuelle metoder i klassen uden parametre og indhold
Opret test metoder ved hjælp af funktionerne i VS
Skriv test og tilføj eventuelle parametre til metoden for at kunne kompilere, men testen fejler stadig
Skriv koden til metoden og se testen gå godt.
Fordelen ved at gøre dette, er at man automatisk undgår mange af de ting der gør det svært at test sin kode, fordi man får tænkt testen ind med det samme. Se eventuelt mit eksempel på en sådan fremgangsmåde her TddDemo.
Unit test bør udføres på generelt alt forretningslogik. Der er sikkert nogle situationer, hvor det ikke er så nemt at skrive sin unit test da man i mange tilfælde vil være afhængig af at en række af objekter er bundet til datakilder, der skal være udfyldt med nogle fornuftige data. Men det bør ikke være en undskyldning for ikke at teste, nærmere en udfordring til at lave sin forretningslogik så simpel og testbar som muligt.
Hvis man i sin forretningslogik har taget udgangspunkt i de krav som der stilles til softwaren, skal det som udgangspunkt være muligt at teste sin kode. Som et eksempel kunne man tage en applikation, hvortil der er stillet følgende krav:
Som en telefonsælger, skal jeg kunne oprette informationer om en ny kunde.
1) Når jeg opretter kunde informationer, skal det sikres at alle informationer er indtastet.
Her vil det være logisk, at lave 2 unit tests. En der kontrollerer om der kan oprettes et objekt af typen kunde og en test af om alle informationer bliver kontrolleret.
Først er her Client klassen der skal testes:
Client.vb
Public Class Client PublicSubNew() 'Dummy constructor EndSub
'All parameters are correct. ReturnTrue EndFunction
... End Class
Og her er de to tests:
ClientTest.vb
<TestMethod()> _ PublicSub ClientConstructorTest() Dim target As Client = New Client
Assert.IsInstanceOfType(target, GetType(Logic.Client), "Client creation failed") EndSub
<TestMethod()> _ PublicSub ValidateTest() Dim target As Client = New Client With{.Name = "Test", .Age = 25} Dim actual AsBoolean
actual = target.Validate
Assert.IsTrue(actual, "Validate client failed") EndSub
En lille morsom note til denne test er, at den faktisk fejlede første gang jeg kørte den. Jeg havde ved en fejl oprettet propertien Name som en integer og fik derved fjernet denne fejl allerede inden jeg havde anvendt min Client klasse til noget.
There are no comments on this page. [Add comment]