(Unit) Testing. Det skal du



Relaterede dokumenter
SWC eksamens-spørgsmål. Oversigt

Begreber om Godt Software

Tredjepart webservices

Design by Contract. Design and Programming by Contract. Oversigt. Prædikater

University of Southern Denmark Syddansk Universitet. DM502 Forelæsning 2

University of Southern Denmark Syddansk Universitet. DM503 Forelæsning 11

Abstrakte datatyper C#-version

Ugeseddel 4 1. marts - 8. marts

A Profile for Safety Critical Java

Dag 10 Flertrådet programmering

Software Construction 1 semester (SWC) Spørgsmål 1

DAXIF# - Delegate Automated Xrm Installation Framework. Delegate A/S

Algoritmeskabeloner: Sweep- og søgealgoritmer C#-version

Systematisk testning af program til udregning af mellemskat

Kursusarbejde 2 Grundlæggende Programmering

Database for udviklere. Jan Lund Madsen PBS10107

AAU, Programmering i Java Intern skriftlig prøve 18. maj 2007

Løsning af skyline-problemet

Eksempel: et ordresystem note 5 Lagdeling s. 1

ADIS, WS og Meta Service

Udvikling af DOTNET applikationer til MicroStation i C#

Succesfuld implementering af automatiseret test

Hvordan vælger jeg dokumentprofilen?

F# - hvorfor, hvordan og til hvad? Rune Ibsen Jyske Bank

Sporbarhed og Rapportering i Quality Center. Kim Stenbo Nielsen NNIT Application Management Services

RMI introduktion. Denne artikel beskriver Java RMI (Remtote Method Invocation).

DM507 Algoritmer og datastrukturer

Plan for præsentationen

Videregående Programmering Obligatorisk opgave - 3. semester, efterår 2004

DM507 Algoritmer og datastrukturer

Nye testteknikker fra ISTQB - direkte fra hylderne. Ole Chr. Hansen

//Udskriver System.out.println("Hej " + ditfornavn + " " + ditefternavn + "."); System.out.println("Du er " + dinalder + " aar gammel!

Affaldsdatasystem Vejledning supplement i system-til-system integration for.net brugere

Software Design (SWD) Spørgsmål 1

Terese B. Thomsen 1.semester Formidling, projektarbejde og webdesign ITU DMD d. 02/

Specifikation Abstrakt OO OS-API Rev Specifikation. Abstrakt, objektorienteret operativsystem-api

Anvendelse af metoder - Programmering

Databaseadgang fra Java

Skriftlig eksamen i Datalogi

Fra idé til virkelig med Azure Mobile Services

DM507 Algoritmer og datastrukturer

University of Southern Denmark Syddansk Universitet. DM502 Forelæsning 3

PARALLELIZATION OF ATTILA SIMULATOR WITH OPENMP MIGUEL ÁNGEL MARTÍNEZ DEL AMOR MINIPROJECT OF TDT24 NTNU

Hvad er Objekter - Programmering

Casper Fabricius ActiveRecord. O/RM i Ruby on Rails

Singleton pattern i Java

FRA USECASE TIL TESTCASE HP TEST BRUGERKONFERENCE, 10. APRIL 2014

Version Dato Beskrivelse /11/2012 Initial version /03/2013 Tilføjet eksempel med Template Agent, generelt udvidet dokumentet.

Specifikationsdokument for PDF Validator API

Objektorienteret Programmering

MapBasic &.NET interaktion. MapBasic.NET. Jakob Lanstorp IT konsulent COWI. Odense 23. Juni jun 2011 MapBasic &.

Kursus i OOP og Java. Kursus i Objektorienteret programmering i Java

Specialeforsvar: Fundamentet for et fleksibelt container bibliotek

Introduktion til funktioner, moduler og scopes i Python

Kursusarbejde 3 Grundlæggende Programmering

DM507 Algoritmer og datastrukturer

Transkript:

(Unit) Testing Det skal du 1

Overblik I dag skal det handle om testing (unit testing) 1. Kort om forskellige former for tests. 2. Unit Testing (Black Box Testing) Opfører kode under test sig som forventet? 3. White Box testing Få aftestet så meget af koden som muligt ved at analysere input værdier 4. Isolerede unit tests Med håndskrevne mocks/stubs eller via Isolations framework 2

Hvad er test og hvorfor teste? Program test er udførelse af et program med den hensigt at finde fejl. Formålet med at teste er, at sikre at et program opfører sig som forventet, og ikke indeholder fejl (bugs). En god test har høj sandsynlighed for at afdække en fejl. Testing er dyrt men fejl er dyrere Jo før vi finder fejl jo bedre. Jo senere fejl opdages, jo dyrere er de at rette. Worst case: Fejl opdages efter produkt er shippet (tilbagekaldelse, kompensation, dårlig pr, etc.) Next-worst case: Fejl kan vise sig at kræve et redesign 3

Forskellige former for tests Forskellige former for tests bruges på forskellige tidspunkter til forskellige ting: Unit Test: Aftestning af programmets mindste enheder (metoder) i isolation. Integration Test: Delene er testet med unit tests Nu testes interaktionen mellem komponenter System Test: Tester systemet som et hele Acceptance Test: Aftestning via slutbruger afviger systemet fra kundens krav? Dækkende unit tests er byggeblokken for efterfølgende tests. 4

Integrations testing Fra The art of Unit Testing, Roy Osherove 5

Hvad er unit tests? En unit test består af en metode der kalder en anden metode (kode under test - CUT) og tjekker korrektheden af bestemte antagelser. Hvis antagelsen er forkert har unit testen fejlet En unit test kan opdeles i tre dele: Arrange: Data der skal bruges som input til testen klargøres. Act: En sekvens der udfører koden under test med data fra Arrange skridtet. Assert: Assertions verificerer korrektheden af bestemte antagelser for koden under test. 6

Unit Test frameworks I princippet kunne vi lave vores eget test framework, men der er ingen grund til at genopfinde hjulet I stedet bruges et unit testing framework (fx NUnit) Unit test frameworks automatiserer tests. Indeholder runners der kan udføre én, flere, eller alle tests på én gang. Indeholder assertions til at validere forventet opførsel Unit testing frameworks letter processen med at skrive tests. 7

Eksempel på en Unit Test Unit tests kan opdeles i Arrange-Act-Assert afdelinger: //I tilstand Q3 skal input 0 rejectes (returner false) [Test] public void Take0_ReturnFalse() { //Arrange Dfa dfa = new Dfa(); Q3State target = new Q3State(dfa); int input = 0; bool expected = false; //Act bool actual = target.take0(index); //Assert Assert.AreEqual(expected, actual); } 8

Getting started med NUnit 1. Download NUnit fra nunit.org 2. Lav (test) projekt der indeholder unit tests af kode-projekt -> vi adskiller tests fra kode. 3. I testkode-projektet: add reference til nunit.framework.dll (../Nunit <version>/bin/net-2.0/framework/) 4. Skriv tests 5. Kør tests: Åbn Nunit -> Åbn test-projekt -> Kør 9

Eksempel test Area() metode Kode-projektet har en Rectangle klasse. - Dens constructor tager højde/breddre parametre. - Dens Area() metode returnerer arealet (højde * bredde). //1. Lav test projekt using NUnit.Framework; Add reference using RectangleProject; //Rectangle defineret her namespace RectangleTestProject { 1. Opret test projekt [TestFixture] 2. Importer nunit og CUT public class RectangleTest 3. Lav test klasse (fixture) { 4. Lav test case [Test] 5. Kør test i Nunit public void TestArea() 1. Åbn og kør testprojekt { Rectangle q = new Rectangle(2, 3); Assert.IsTrue(q.Area() == 6); } } 10

[TestFixture] Opsætning af fælles ressource (indlæs fra DB el. lign public class RectangleTest { [TestFixtureSetUp] <- Udføres én gang før første test public void SetUpSuite() {...} [TestFixtureTearDown] <- Udføres én gang efter sidste test public void TearDownSuite() {...} [TearDown] <- Udføres efter hver test public void TearDownTest() {...} [SetUp] <- Udføres før hver test public void TestCaseSetup() {...} Isolation er vigtigt } [Test] public void SomeUnitTest() { Rectangle r = new Rectangle(2, 3); int area = q.area(); Assert.IsTrue(q.Area() == 6); } //Arrange //Act //Assert Importer Kode-projekt Unit test 11

Test attributter Følgende grundlæggende (og stort set identiske) attributter findes i ethvert unit testing framework: TestFixture, Test, SetUp, TearDown, TestFixtureSetUp, TestFixtureTearDown, ExpectedException, parametriseret test) [ExpectedException(ExceptionType)] Angiver forventet exception. Hvis exception = pass, hvis ikke = fail Parametriseret test kan angive forskellige input værdier til samme test: [TestCase(15, 3, Result=5)] Assert her [TestCase(24, -4, Result = -6)] public double Divide_ValidNumbers_ValidResult(int a, int b) { Person p = new Person(); return p.divide(a, b); } 12

ASSERTIONS 1. EQUALITY ASSERTS 2. IDENTITY ASSERTS 3. CONDITION ASSERTS 4. COMPARISON ASSERTS 5. TYPE ASSERTS 6. EXCEPTION ASSERTS 7. STRING ASSERT 8. COLLECTION ASSERT 9. UTILITY METHODS Assertions Assertions bruges til at verificere at koden under test opfører sig som forventet. Assertions er opdelt i følgende kategorier: 13

1 og 2: Equality og Identity Asserts Equality Assertions Assert.AreEqual(expected, actual) Numeriske værdier og object. Assert.AreEqual(expected, actual, delta) Assert.AreNotEqual(expected, actual) Numeriske værdier og object Identity Assertions Assert.AreSame(expected, actual) //reference til samme objekt? Assert.NotSame(expected, actual) Alle asserts har overload der tillader at man kan angive en besked til brugeren når assert fejler. 14

3. Condition Asserts Assert.IsTrue(bool) Assert.IsFalse(bool) Assert.IsNaN(double) Assert.IsEmpty(string) Assert.IsNotEmpty(string) Assert.IsEmpty(ICollection) Assert.IsNotEmpty(Icollection) 15

4. Comparison Asserts Assert.Greater(arg1, arg2) Assert.GreaterOrEqual( int arg1, int arg2 ); Assert.Less(arg1, arg2) Assert.LessOrEqual( int arg1, int arg2 ); Gælder for numeriske værdier og IComparable 16

5. Type Asserts Assert.IsInstanceOf( Type expected, object actual ); Assert.IsNotInstanceOf( Type expected, object actual ); Assert.IsAssignableFrom( Type expected, object actual ); Kan objekt tildeles værdi af given type? Assert.IsNotAssignableFrom( Type expected, object actual ); Generiske udgaver (fra version 2.5): Assert.IsInstanceOf<T>( object actual ) Assert.IsNotInstanceOf<T>( object actual ) Assert.IsAssignableFrom<T>( object actual ); Assert.IsNotAssignableFrom<T>( object actual ); 17

6. Exception Asserts a) Exception Assert.Throws( Type expectedexceptiontype, TestDelegate code); b) T Assert.Throws<T>(TestDelegate code); Throws forsøger at kalde en metode (indkapslet som delegate) for at verificere at metoden kaster den angivne exception. void Assert.DoesNotThrow(TestDelegate code); Verificerer at delegate ikke kaster exception. a) Exception Assert.Catch(TestDelegate code); b) T Assert.Catch<T>(TestDelegate code); Verificerer at delegate kaster exception af angivne type eller en nedarvet exception type. [Test] public void TestException() { //throws returnerer exception, der så kan inspiceres ArgumentException ex = Assert.Throws<ArgumentException>( () => { throw new ArgumentException("bad bad arg") }); Assert.That(ex.Message, Is.EqualTo("bad bad arg")); } 18

Samme throws test i forskellige versioner [Test] public void Tests() { //.NET 1.x Assert.Throws(typeof(ArgumentException), new TestDelegate(MethodThatBlows) ); //.NET 2.0 Assert.Throws<ArgumentException>( MethodThatBlows() ); Assert.Throws<ArgumentException>( delegate { throw new ArgumentException(); } ); } // C# 3.0 Assert.Throws<ArgumentException>( () => throw new ArgumentException(); } ); void MethodThatBlows() { throw new ArgumentException(); } http://www.nunit.org/index.php?p=exceptionasserts&r=2.5.8 19

7. String Asserts StringAssert.Contains( string expected, string actual ); StringAssert.StartsWith( string expected, string actual ); StringAssert.EndsWith( string expected, string actual ); StringAssert.AreEqualIgnoringCase( string expected, string actual ); StringAssert.IsMatch( string regexpattern, string actual ); 20

8. Collection Asserts CollectionAssert.AllItemsAreInstancesOfType( IEnumerable collection, Type expectedtype ); CollectionAssert.AllItemsAreNotNull( IEnumerable collection ); CollectionAssert.AllItemsAreUnique( IEnumerable collection ); CollectionAssert.AreEqual( IEnumerable expected, IEnumerable actual ); CollectionAssert.AreNotEqual( IEnumerable expected, IEnumerable actual ); CollectionAssert.AreEquivalent( IEnumerable expected, IEnumerable actual); CollectionAssert.AreNotEquivalent( IEnumerable expected, IEnumerable actual ); CollectionAssert.Contains( IEnumerable expected, object actual ); CollectionAssert.DoesNotContain( IEnumerable expected, object actual ); CollectionAssert.IsSubsetOf( IEnumerable subset, IEnumerable superset ); CollectionAssert.IsNotSubsetOf( IEnumerable subset, IEnumerable superset); CollectionAssert.IsEmpty( IEnumerable collection ); CollectionAssert.IsNotEmpty( IEnumerable collection ); CollectionAssert.IsOrdered( IEnumerable collection ); CollectionAssert.IsOrdered( IEnumerable collection, IComparer comparer ); 21

9. Utility metoder Assert.Pass(); Tillader en test at bestå øjeblikkeligt Assert.Fail(); Tillader en test af fejle øjeblikkeligt Assert.Ignore(); Angiver at testen midlertidig skal ignoreres Assert.Inconclusive(); Angiver at testen er inconclusive 22

Brug en konvention Anbefaling fra The Art of Unit Test af Roy Osherove (arkitekten bag Test Lint): Target Test Projekt Klasse Lav projekt kaldet [Projekt].Tests. - MySuperProject -> MySuperProject.Tests Lav klasse kaldet [Klasse]Tests. - Car -> CarTests Metode Lav (minimum én) metode kaldet [Metode]_[Betingelse]_[Forventning]. Fx. isstrongpassword_strongpass_returnstrue IsStrongPassword_weakPass_ReturnsFalse 23

Fordele ved unit testing Unit tests kan bruge til at angive funktionelle krav: Givet at input overholder en precondition, så tjekkes at output overholder en postcondition Test Driven Development starter med tests inden koden skrives tests er drivkraften i designet. En test-suite med høj kode-dækning og mange assertions indikerer kode af god kvalitet. Unit tests kan bruges til at dokumentere korrekt programopførsel. Kort feedback loop fejl opdages og rettes hurtigt. Unit tests fungerer som regressions-tests. Når vi ændrer i koden, fungerer det så stadig? 24

Ulemper ved unit testing Kan være trivielt og tidskrævende Kvaliteten af unit tests afhænger af kvaliteten af unit tests (resultat af den investerede tid samt testernes erfaring) Kvalitet afhænger af kode dækning og antal assertions (kode-dækning er ikke i sig selv en garanti, men lav kode-dækning indikerer en risiko) Unit tests kan indeholde bugs + være svære at vedligeholde. 25

Test-Driven Development (TDD) Udviklingsproces hvor test skrives inden koden. Tests fungerer som funktionel specifikation TDD består af 3 korte skridt: 1. Skriv en test 2. Kør den og se den fejle 3. Refaktorer koden så testen består. 26

fejl Fra The Art of Unit Testing: with Examples in.net 27

Regler for test cases En test case skal validere én ting (én Assert) Hvis én assert fejler i en metode udføres resten ikke. Flere asserts betyder flere tests En test skal være simpel og læsbar > nem at forstå; undgå at introducere bugs i testen. Betinget logik er forbudt -> split op i flere tests el. params. Undgå False positives Undgå False negatives En test case skal være reproducerbar. Samme input -> samme output Samme tilstand før hver udførelse af test. Test cases skal udføres i isolation Test cases skal kunne udføres i vilkårlig rækkefølge En test case køres udelukkende i main memory. Den rører ikke nogle eksterne ressourcer (filer eller databaser). Hvis det er tilfældet er det ikke en unit test, men en integrationstest. En sådan test ændrer tilstand for andre tests/senere tests (ikke reproducerbar, ikke vilkårlig rækkefølge). 28

Flere regler Gør tests vedligeholdelsesbare Test kun offentlige medlemmer Tests bliver mindre skrøbelige Private medlemmer bliver indirekte testet gennem offentlige medlemmer Don t Repeat Yourself (DRY): Undgå redundant kode. Placer fælles Arrange i metoder (InitializeXXX) Undgå new() brug Factory i tilfælde af constructor ændres. Test logik skal angive forventet opførsel Fail first betyder ikke at skriv en dummy test. Dette medfører at testen ikke koden skal ændres for at få pass Lad Test API fungere som dokumentation -> Brug sigende test navne, giv sigende beskeder ved Assert fejl. Gør tests nemme at køre Hvis de er svære gider folk ikke -> fejl bliver ikke opdaget Flyt (konfigurations-) krævende tests til separat mappe/projekt med instruktioner. 29

Hvad skal der testes efter? Forventet opførsel ved forskelligt input Test for grænseværdier Find først ud af hvad grænseværdierne er (ækvivalens klasser og grænseværdi analyse) Test både for success og fejl Opfører koden under test sig som forventet ved både lovligt og ulovlig input 30

Input til test Black Box Testing: Drevet af funktionelle krav. Givet bestemt input skal systemet give bestemt output Udtømmende kombinationer af input værdier er ikke muligt (2^32 forskellige ints, et ubegrænset antal strings, ) Mål: En test skal maksimere antallet af fejl der findes med et endeligt antal test cases Hvordan finder vi vores endelige antal test cases? Analyse af input/output domænet Heuristikker 31

Input/Output analyse Baseret på ækvivalens partitionering: Input deles ind i et antal klasser (partitioner) hvor følgende antages: Programmet opfører sig ens for input fra samme klasse (all fail all pass) En test med en værdi fra hver klasse er tilstrækkeligt (repræsentativ jf 1) Ækvivalens-klasser kan afgøres fra specifikation (1 <= x <= 10) Mere effektivt er, at inkludere værdier på, lige over, og lige under grænserne -> 32

#1 Ækvivalens klasse Værdi Bemærkning 1 x < 1-100 Tilfældigt tal under nedre grænse 2 1 <= x <=10 0 Lige under nedre grænse 3 1 <= x <=10 1 På nedre grænse 4 1 <= x <=10 2 Lige over nedre grænse 5 1 <= x <=10 4 Tilfældig tal imellem 6 1 <= x <=10 9 Lige under øvre grænse 7 1 <= x <=10 10 På øvre grænse 8 1 <= x <=10 11 Lige over øvre grænse 9 X > 10 65 Tilfældig tal over øvre grænse 33

Heuristikker Vi kan foretage risiko-analyse for at identificere høj-risiko kode: Enheder med uklar specifikation Kode skrevet af uerfarne programmører Kompleks kode Risiko analyse beror på vurdering. 34

Kode-dækning Testing never proves the absence of faults, it only shows their presence (Dijkstra) Selv hvis alle vores tests består, betyder det så vores kode er skudsikker? Hvad nu hvis vi kun har testet halvdelen af koden? Kode-dækning (test dækning) er en måde at finde områder i koden der ikke er dækket af tests, og til at definere yderligere tests til at øge dækning Kode-dækning er indirekte et kvalitets-kriterie manglende dækning indikerer lav troværdighed Kode-dækning bygger på antagelsen om at mange fejl er relateret til control flow Kode-dækning kræver at vi dykker ned i koden - white box testing 35

White-box Testing Black-box testing udvælger tests på baggrund af funktionelle krav White-box testing udvælger tests baseret på et programs interne struktur White-box testing omhandler kode-dækning kriteriet for udvælgelse af tests er Adequacy Criteria Adequacy kriteriet afgøres af hvor godt en test suite har dækket en Control Flow Graph (CFG) (eller anden model af koden) CFG er en orienteret graf hvor knuder er (en region af) kode, og kanter repræsenter skift mellem koderegioner. En region har et enkelt entry og exit punkt. 36

Forskellige metrikker Funktions-coverage: Har vi besøgt alle metoder? Statement coverage: Har vi besøgt alle knuder i CFG? Decision/Branch coverage: Har vi besøgt alle kanter i CFG? Condition coverage: Har hvert boolsk (sub) udtryk været evalueret til hhv sand og falsk? Kode-dæknings værktøjer inkluderer typisk statement- og branch coverage. 37

Foo(int x, int y, int z) { if (x > 1 && y == 0) z = z / x; if (x == 2 z > 1) z = z + 1; } 1) X = 2, Y = 0, Z = 3 (abe) statement coverage 1) X = 2, Y = 0, Z = 3 (abe) 2)X = 0, Z = 0 (acd) branch coverage 1) X = 2, Y = 0, Z = 0 (acd) 2) X = 3, Y = 0, Z = 3 (ace) Condition coverage (ikke branch coverage) 38

Demo Kode dækning i Visual Studio Lav unit test projekt Enable code coverage: Test -> Edit Test Settings -> Local -> Data and Diagnostics -> Dobbelt-klik code coverage -> angiv kode under test. Kør unit test Non VS Ncover (http://ncover.sourceforge.net/ ) 39

Kode dækning (2) I Test Results : Kode under test: Rød = ikke besøgt, blå = besøgt 40

Pex Program EXploration Automatiseret test input generator Branch coverage Hvordan Pex finder input -> (Slides fra http://research.microsoft.com/enus/projects/pex/documentation.aspx) -> Download PEX og Moles (Academic version): http://research.microsoft.com/en- us/downloads/d2279651-851f-4d7a-bf05-16fd7eb26559/default.aspx 41

Dynamic Symbolic Execution Code to generate inputs for: void CoverMe(int[] a) { if (a == null) return; if (a.length > 0) if (a[0] == 1234567890) throw new Exception("bug"); } F F a.length>0 a==null T T Constraints to solve a!=null a!=null && a.length>0 a!=null && a.length>0 && a[0]==1234567890 Solve Choose next path Data null {} {0} {123 } Execute&Monitor Observed constraints a==null a!=null &&!(a.length>0) a!=null && a.length>0 && a[0]!=1234567890 Negated condition a!=null && a.length>0 && a[0]==1234567890 Done: There is no path left. F a[0]==123 T 42

Aftestning i isolation Næste sektion omhandler brugen af stubs og mocks til at bryde eksterne afhængigheder Download NMock (Nunit har også indbyggede mocks, men de er lavet til internt brug dokumentationen er ikke-eksisterende) Add reference: NMock2.dll + using NMock2; 43

Aftestning i isolation Metoder på ét objekt afhænger tit af metoder på andre objekter Disse afhængigheder kan være: uden for vores kontrol/ tidskrævende/ besværlige/ endnu ikke implementeret (filsystem, db, kald af web services, send mail, etc.) Vi vil gerne fjerne disse afhængigheder, så vi kan teste i isolation -> Vi introducerer istedet Mocks (Stubs) 44

Stub Stub: En kontrollerbar erstatning for en ekstern afhængighed. Ekstern afhængighed: Et objekt som koden under test interagerer med og som vi ikke har kontrol over/tidskrævende el. lign. (fil manager, mail manager, db persistens, etc.). Stubs gør at vi kan teste kode, uden at bekymre os om eksterne afhængigheder. Hvad der sker i de eksterne afhængigheder er uvæsentligt for den nuværende test Ved at fjerne afhængigheder gør vi vores kode testbar 45

Udskift afhængighed med Stub Lav et interface som afhængigheden arver fra (hvis ikke det er gjort i forvejen) Programmér til et interface (hvis ikke det er gjort i forvejen) Lav stub der arver fra interface 1. Kan laves i hånden 2. Man kan også et isolations-framework (som fx NMock) Udskift afhængighed med stub (Dependency Injection) 46

Eksempel med Shopping basket Vi vil afteste ShoppingBasket samt BasketItem ShoppingBasket gemmer indehold af kurv i DB BasketItem indlæser, givet et produktid, produktets navn og pris fra databasen. Database = ekstern afhængighed Vi vil gerne teste uden at kommunikere med den fysiske database. 47

Interface for ekstern afhængighed //interface for external dependency (og stub) public interface IShoppingDataAccess { string GetProductName(int productid); decimal GetUnitPrice(int productid); IList<BasketItem> LoadBasketItems(Guid basketid); void SaveBasketItems(Guid basketid, IList<BasketItem> items); } //En database (= external dependency) public interface ShoppingDB : IShoppingDataAccess { public string GetProductName(int productid) {...} public decimal GetUnitPrice(int productid) {...} public IList<BasketItem> LoadBasketItems(Guid basketid) {...} void SaveBasketItems(Guid basketid, IList<BasketItem> items) {..} } 48

public class BasketItem Dependency { IShoppingDataAccess _dataaccess; public decimal UnitPrice { get; set; } public string ProductName { get; set; } private int _productid; Ekstern kommunikation public int ProductId { get { return _productid; } set { _productid = value; UnitPrice = _dataaccess.getunitprice(_productid); ProductName = _dataaccess.getproductname(_productid); } } Dependency Injection } public BasketItem( int productid, int quantity, IShoppingDataAccess dataaccess) { this.initialize(productid, quantity, dataaccess); }... 49

public class Basket { List<BasketItem> _basketitems; private Guid _baskedid; //bruger også db/stub private IShoppingDataAccess _dataaccess; Dependency og Dependency Injection public Basket(IShoppingDataAccess dataaccess) { Initialize(dataAccess); }... } public void Save() { _dataaccess.savebasketitems( _baskedid, _basketitems.asreadonly()); } 50

Og her er så en håndskreven stub: //interfacet for external dependency (og stub) public interface IShoppingDataAccess { string GetProductName(int productid); decimal GetUnitPrice(int productid); IList<BasketItem> LoadBasketItems(Guid basketid); void SaveBasketItems(Guid basketid, IList<BasketItem> items); } //Vores stub for databasen //Returner dummy værdier (fixed el. conditional) ved kald til Stub public interface ShoppingDBStub : IShoppingDataAccess { public string GetProductName(int productid) { } public decimal GetUnitPrice(int productid) {...} public IList<BasketItem> LoadBasketItems(Guid basketid) {...} void SaveBasketItems(Guid basketid, IList<BasketItem> items) {..} } 51

Simpel Test med afhængighed fjernet [TestFixture] public class BasketTests { [Test] public void SimpleSaveTest() { ShoppingDBMock dbmock = new ShoppingDBMock(); Basket basket = new Basket(dbMock); BasketItem item = new BasketItem(1, 2, dbmock); basket.add(item); b.save(); <- Mock implementation kaldes //Assert opdateret tilstand Assert.Contains(item, basket.basketitems); } } 52

Vi kan også spare krudt på at lave vores egen stub, og i stedet lade NMock gøre det for os using NMock2; [Test] mock objekt fabrik public void SimpleSaveTest() { Nyt mock objekt vha Reflection NMock2.Mockery mockery = new Mockery(); IShoppingDataAccess dbmock = mockery.newmock<ishoppingdataaccess>(); Basket b = new Basket(dbMock); Guid g = Guid.NewGuid(); b.basketid = g; Stub.On(dbMock). Method("SaveBasketItems"). With(g, new List<BasketItem>()); Mock objekt til hvis SaveBasketItems bliver Kaldt med angivne parametre (stub = loose mock) ~SetupResult; strict = false } b.save(); //kalder SaveBasketItems mockery.verifyallexpectationshavebeenmet(); //til ære for Lint 53

Mock (eller strict mock) udgave using NMock2; [Test] mock objekt fabrik public void SimpleSaveTest() { Nyt mock objekt vha Reflection NMock2.Mockery mockery = new Mockery(); IShoppingDataAccess dbmock = mockery.newmock<ishoppingdataaccess>(); Basket b = new Basket(dbMock); Guid g = Guid.NewGuid(); b.basketid = g; Expect.Once.On(dbMock). Method("SaveBasketItems"). With(g, new List<BasketItem>()); SaveBasketItems skal kaldes én gang med angivne parametre } b.save(); //kalder SaveBasketItems mockery.verifyallexpectationshavebeenmet(); 54

Input værdier og returværdier Mocks/stubs kan også returnere forskellige værdier ved forskelligt input Aftestning af BasketItems constructor der får værdier indlæst fra DB på baggrund af angivet productid 55

public class BasketItem { IShoppingDataAccess _dataaccess; public decimal UnitPrice { get; set; } public string ProductName { get; set; } public int Quantity { get; set; } private int _productid; public int ProductId { get { return _productid; } set { _productid = value; UnitPrice = _dataaccess.getunitprice(_productid); ProductName = _dataaccess.getproductname(_productid); } } } public BasketItem(int p, int q, IShoppingDataAccess d) { this.dataaccess = d; this.productid = p; this.quantity = q; } 56

[Test] public void SimpleSaveTest() { IShoppingDataAccess dbmock2 = mockery.newmock<ishoppingdataaccess>(); Mocks med returværdier int expectedproductid = 1, expectedquantity = 2; decimal expectedunitprice = 99.0m, expectedsubtotal = 198.0m; string expectedproductname = "Cheese"; //sæt forventninger Expect.Once.On(dbMock2). Method("GetUnitPrice"). With(expectedProductId). Will(Return.Value(expectedUnitPrice)); Expect.Once.On(dbMock2). Method("GetProductName"). With(expectedProductId). Will(Return.Value(expectedProductName)); //to be continued -> 57

Fortsat (constructoren kaldes)...fortsat //her kommer kaldet til constructoren: BasketItem item = new BasketItem(expectedProductId, expectedquantity, dbmock2); //Assert korrekt tilstand Assert.AreEqual(item.ProductId, expectedproductid); Assert.AreEqual(item.Quantity, expectedquantity); Assert.AreEqual(item.ProductName, expectedproductname); Assert.AreEqual(item.UnitPrice, expectedunitprice); Assert.AreEqual(item.GetPrice(), expectedsubtotal); } //korrekt opførsel (metoder der blev kaldt) mockery.verifyallexpectationshavebeenmet(); Se mere på: http://www.nmock.org/cheatsheet.html 58

Forskel på mocks og stubs Stubs bruges til at returnere data til metode under test, så man kan lave assertions på hvordan metoden håndterer data fra afhængigheder. Hvordan afhængigheder kommer frem til resultatet er ikke væsentligt. Mocks bruges til at specificere forventninger om hvordan metoderne på en afhængighed kaldes af metode under test: Hvor mange gange, med hvor mange argumenter, etc. Forskel i hvordan test resultater verificeres: Tilstands-verifikation vs. Behavior verifikation. Med stubs undersøger vi at metode under test er i bestemt tilstand efter kald til afhængigheder. Med mocks undersøger vi at metode under test kalder afhængigheder på den rigtige måde. Det eneste der assertes på er, at eksterne afhængigheders metoder blev kaldt i den rigtige rækkefølge. Metode under test ændrer ikke nødvendigvis tilstand. 59

Værktøjer NUnit: http://www.nunit.org/ NMock: http://www.nmock.org/ Typemock Test Lint: http://site.typemock.com/test-lint/ NCover: http://ncover.sourceforge.net/ Pex (og Moles): http://research.microsoft.com/en-us/projects/pex/ Alternative værktøjer: Unit Testing Framework: MBUnit: http://www.mbunit.com/ Isolations-framework: Rhino Mocks: http://www.ayende.com/projects/rhino-mocks.aspx Moq: http://code.google.com/p/moq/ 60

Opsummering Unit Testing (Black Box Testing) Opfører kode under test sig som forventet? Fortæller ikke noget om hvorvidt vi får aftestet hele koden White Box testing Få aftestet så meget af koden som muligt ved at analysere input værdier Fortæller ikke noget om hvorvidt koden opfylder specifikation Isolerede unit tests Med håndskrevne mocks/stubs eller via Isolations framework 61