Testing / TDD

TDD, BDD og automatisert testing; viktige begreper for den moderne utvikler. Jeg deler mine erfaringer på dette i denne kategorien.

Å jobbe effektivt med drittkode

CropperCapture[44]Michael C. Feathers’ Working Effectively with Legacy Code er en ikke direkte ukjent bok. Jeg leste den i sommer, som en slags forberedelse til å begynne i ny jobb. Den hjalp meg til å hoppe uredd og med stor iver inn en voksende kodebase med varierende kvalitet. For dem som ikke kjenner Michaels mesterverket fra før, her er en kort beskrivelse:

Uttrykket “Legacy Code” brukes i utgangspunktet om gammel kode, kode som man arver fra andre. Benevnelsen innebærer at det er vanskelig å forså hvordan koden fungerer, og det er vanskelig å gjøre endringer. Michael Fethers definisjon på Legacy Code er kode uten automatiserte tester, fordi slik kode nettopp er vanskelig å forstå og å endre. Kode med tester derimot vil gi deg øyeblikkelig tilbakemelding på om du har ødelagt noe, og om endringene du har gjor fungerer som forventet.

Working Effectively with Legacy Code handler i bunn og grunn kun om én ting; nemlig hvordan man får på plass automatiserte tester rundt kode som i utgangspunktet ikke er designet for det, slik at man kan jobbe videre med koden uten å være redd. Gjennom kapitler som I Don’t Have Much Time and I Have to Change It og I Don’t Understand the Code Well Enough to Change It lærer Michael oss pragmatiske og presise teknikker som gjør oss mere komfortable med å ta i “drittkode”.

Kapitler som I Can’t Get This Class into a Test Harness, med avsnitt som The Case of the Irritating Parameter og The Case of the Hidden Dependency er gull verdt, og åpner øynene dine for hvordan det som ser helt håpløst ut faktisk kan løses. Dette er en oppskriftsbok, med en lang liste teknikker Feathers har funnet effektive i sin praksis. Mange av teknikkene er fokusert rundt det å bryte avhengigheter, som for eksempel Break Out Method Object, Expose Static Method, Extract and Override Call, Extract Implementer, Pull Up Feature og Push Down Dependency.

Hvem som bør lese denne boken, og hvorfor..

Denne boken har gitt meg en helt ny holdning til “dårlig kode”. Kode som er vanskelig å endre og som ikke har enhetstester er ikke lenger noe jeg gruer meg til å ta i – jeg ser nå på det som en spennende utfordring. Jeg gleder meg til å sette tenna i metoder på over tusen linjer og ekstremt høy kompleksitet. Helt seriøst, denne boken har tatt noe av det jeg likte minst ved yrket mitt, og gjort det om til noe av det jeg liker mest!

Hvis du kun jobber i perfekte softwareprosjekter, med elegant kode og høy test coverage, så har du ikke bruk for Michaels bok.

Ikke særlig mange sliker jobber, er det vel?

Hvis du derimot er på et team som sliter med en stor og uregjerlig kodebase.., hvis dere har problemer med å få laget gode enhetstester – eller å komme i gang med automatisert testing i det hele tatt.., hvis dere ikke er fornøyd med kvaliteten på koden dere jobber med, og den er vanskelig å forstå.., ja, da er Working Effectively with Legacy Code et uvurderlig hjepemiddel som dere bare må lese med en eneste gang. Legg ned utviklingen et par-tre dager, det er det faktisk verdt om utviklerne lærer seg disse teknikkene og lar seg inspirere til å bruke dem.

Onion architecture

en løk Onion Architecture er navnet Jeffrey Palermo har foreslått for å beskrive et software design pattern som er mye brukt spesielt i Alt.Net-miljøet. Palermo presanterte arkitekturen i en blogpost-serie i juli/august 2008.

Arkitekturen fokuserer på hvordan vi håndterer avhengigheter i programvaren vår, og er et steg bort fra den tradisjonelle lagdelingen vi alle har vært vandt til. Den beskrives som en “løk” med flere lag, hvor kjernen inneholder domenet vi modellerer og forretningslogikken, mens infrastruktur og brukergrensesnitt befinner seg helt ytterst. Alle avhengigheter går innover i løken – ingen ting får lov til å avhenge av noe i et lag lengre ute enn seg selv. Gjennom mye bruk av Dependency Inversion Principle designer man slik en løsning hvor forretningsverdien er skjermet, og ting som endres oftere og som har flere eksterne avhengigheter (UI teknologi, databaser etc) ligger på utsiden og kan lettere byttes ut.

La oss som et konkret eksempel ta en titt på hvordan løsningen jeg utviklet i TDD i praksis og Validering kan være et domene-ansvar ser ut i denne typen løk-modell (klikk på løken for full størrelse om du ikke er langsynt):

onion

Helt innerst i arkitekturen ligger domenemodellen, som i mitt tilfelle er User-klassen. Den får ikke lov til å referere noe annet som ikke også ligger i domenemodellen.

Laget utenfor kaller vi for Domain Services – domenetjenester. Her finner man de tjenestene som har mest med forretningslogikken å gjøre. I mitt eksempel er dette blant annet spesifikasjonen for hva som er en gyldig brukerregistrering – d.v.s. ValidRegistrationSpecification, og alle de andre klassene som inngår i den. Som du ser på tegningen avhenger denne spesifikasjonen først og fremst av User-klassen, men én av spesifikasjonene avhenger også av en annen domenetjeneste, nemlig den abstrakte UserRepositor’en. Jeg vet ikke om alle er helt enige i at en repository er en domenetjeneste – jeg har bl.a. sett noen definere et eget lag mellom domenemodellen og domenetjenestene for repositoriene.

Ett nivå lengre ute finner vi Application Services – tjenester som har mest å gjøre med operasjonene man skal kunne gjøre med programvaren. Jeg definerer f.eks. både controller’en og det abstrakte view’et i Model View Presenter til å være en del av dette laget. Disse klassene er mer eller mindre uavhengig av hvilken presentasjonsteknologi som er brukt, og skal derfor ikke ha noen avhengigheter til laget utenfor, slik som reglene i Onion Architecture dikterer. De bruker derimot domenetjenestene og domenemodellen til å utføre handlinger initiert av brukeren.

I det ytterste laget finner vi de konkrete implementasjonene som er avhengig av spesifik teknologi knyttet til brukergrensesnitt og infrastruktur. I mitt tilfelle er det de konkrete view’ene (ASP.NET brukerkontrollene) og den konkrete repositori’en (som bruker en db4objects-database). Vi kan også plassere testene våre her, for å illustrere hvordan de anhenger av hele arkitekturen og bruker et bestemt testrammeverk. Mens klassene i lagene nedenfor kan betraktes som POCO’s (Plain Old CLR Objects), er klassene i dette ytterste laget som du forstår tettere knyttet bestemte teknlogier. Gjennom å følge prinsippene i Onion Architecture har vi gjort oss mindre sårbare for endringer i disse teknologiene.

Det er vedt å merke seg at Onion Architecture har mye til felles med Evans’ Domain-Driven Design, men at det ikke følger “boka” punkt for punkt. Faktisk mener jeg at “reglene” i denne arkitekturen er mere rigide på noen punkter, men også mere universelt anvendelige.

Inversion of Control

UserRepository og Db4oUserRepository er kanskje den aller tydeligste bruken av Dependency Inversion i dette eksempelet. Som du ser avhenger tjenestene i løsningen av den abstrakte UserRepository, mens “ingenting” avhenger av Db4o-varianten. Ingenting er i hermetegn, fordi den jo må instantieres ett eller annet sted.

Når man følger disse prinsippene, og ikke minst driver frem designet gjennom testdreven utvikling, får man MANGE slike “løse” avhengigheter, og det er her Inversion of Control-rammeverk (IoC)kommer inn i bildet. Dependency Inversion Principle gir oss et design som er lettere å endre, men som er vanskeligere å initialisere. IoC er enkelt fortalt en slags startmotor for programvaren din. Arkitekturen er veldig løs, men IoC-containeren syr alt sammen igjen. Bruk av IoC-rammeverk er derfor ikke livsviktig, men veldig praktisk, når man følger Onion Architecture.

Det finnes en bra sampling med IoC-rammeverk for .NET-utviklere, og er dette ukjent materie for deg bør du absolutt ta en titt. Ikke forvent øyeblikkelige resultater, men ta deg god tid, og forsøk å introdusere bruken gradvis. Noen av de mest kjente rammeverkene er: Castle Windsor, StructureMap, Spring.NET, og min personlige favoritt Ninject. Anbefaler dessuten å ta en titt på relevante DimeCasts for å få en god start på dependency injection.

Så det var min introduksjon til Onion Architecture, pluss en liten rant om IoC – jeg håper det gav mening. Det ryktes forresten at Fredrik Kalseth snakker om Onion Architecture på MSDN Live som pågår i disse dager. Det er fortsatt ikke for sent å melde seg på til showene i Bergen (22. sep), Trondheim (24. sep) og Oslo (29. sep).

Noen knagger: , , , , , , ,

Validering kan være et domene-ansvar

Hvis du skummet gjennom min forrige post, TDD i praksis, så observerte du meg implementere registrering av ny bruker. Selv om det var en lang artikkel var det (minst) en ting jeg utelot der – det var nemlig ingen validering av input. Alle registreringer ville blitt en suksess uansett hva man tastet inn.

Validering kan gjøres på mange måter, men for meg er det for det første et eget ansvar som fortjener å bli implementert som egne tjenester/moduler, og for det andre er det et domene-ansvar, og bør ikke knyttes til infrastrukturen eller brukergrensesnittet (denne påstanden gjelder ikke for de mest banale valideringene, og igjen vil noen kunne kalle denne approachen for overkill).

Merk at dette er en konklusjon jeg mer eller mindre har funnet på av meg selv – en fremgangsmåte som gir mening for meg – så hvis du har noen kommentarer og innvendinger så er de som alltid hjertelig velkomne!

Jeg vil benytte anledningen til å implementere validering ved hjelp av specification pattern. Jeg har blogget om dette før, så jeg vil ikke ta med alle detaljene, men jeg vil spesielt vise hvilke tester jeg har skrevet for validering av input. Jeg skrev en og en test, og sørget for at hver test ble grønn før jeg gikk videre, men her har du hele testklassen i one go. Bruk litt tid på å lese gjennom dem..

  103 [TestFixture]

  104     public class When_validating_a_registration

  105     {

  106         Mock<UserRepository> mockRepository;

  107         Specification<User> validator;

  108         User user;

  109 

  110         [SetUp]

  111         public void SetUp()

  112         {

  113             mockRepository = new Mock<UserRepository>();

  114             validator = new ValidRegistrationSpecification(mockRepository.Object);

  115             // Default valid user registration

  116             user = new User() { Username = “T-Man”, Password = “abcd1234#” };

  117         }

  118         [Test]

  119         public void Should_validate_a_strong_password() {

  120             Assert.That(validator.IsSatisfiedBy(user));

  121         }

  122         [Test]

  123         public void Should_not_validate_password_without_letters() {

  124             user.Password = “12345678#”;

  125             Assert.IsFalse(validator.IsSatisfiedBy(user));

  126         }

  127         [Test]

  128         public void Should_not_validate_password_without_digits()

  129         {

  130             user.Password = “abcdefgh#”;

  131             Assert.IsFalse(validator.IsSatisfiedBy(user));

  132         }

  133         [Test]

  134         public void Should_not_validate_password_without_special_character()

  135         {

  136             user.Password = “abcd12345″;

  137             Assert.IsFalse(validator.IsSatisfiedBy(user));

  138         }

  139         [Test]

  140         public void Should_not_validate_password_with_less_than_8_characters()

  141         {

  142             user.Password = “abc123#”;

  143             Assert.IsFalse(validator.IsSatisfiedBy(user));

  144         }

  145         [Test]

  146         public void Should_not_validate_usernames_with_less_than_4_characters()

  147         {

  148             user.Username = “MrT”;

  149             Assert.IsFalse(validator.IsSatisfiedBy(user));

  150         }

  151         [Test]

  152         public void Username_can_not_be_accepted_if_it_already_exist()

  153         {

  154             var mockRepository = new Mock<UserRepository>();

  155             mockRepository

  156                 .Setup(rep => rep.Find(It.IsAny<Predicate<User>>()))

  157                 .Returns(new[] { new User() });

  158             Assert.IsFalse(validator.IsSatisfiedBy(user));

  159         }

  160     }

Metodenavnene i testklassen gir en perfekt oppsummering av reglene for hva en som er en gyldig brukerregistrering. Et passord må inneholde bokstaver, tall, spesialtegn – totalt minst 8 stykker, og brukernavnet må være minst 4 tegn, og ikke eksistere fra før. Nå kunne jeg ha implementert denne valideringen med noen ganske få linjer kode, men jeg vil at designet skal reflektere kunnskapen om reglene, og at det skal være veldig enkelt å endre en og en regel, og å legge til eller fjerne regler. ValidRegistrationSpecification ser derfor slik ut:

   78 public class ValidRegistrationSpecification : Specification<User>

   79 {

   80     private List<Specification<User>> _specifications;       

   81     public ValidRegistrationSpecification(UserRepository repository)

   82     {

   83         _specifications = new List<Specification<User>>();

   84         _specifications.Add(new UsernameShouldBeAtLeastFourCharacters());

   85         _specifications.Add(new UsernameMustBeAvailable(repository));

   86         _specifications.Add(new PasswordMustHaveLetter());

   87         _specifications.Add(new PasswordMustHaveDigit());

   88         _specifications.Add(new PasswordMustHaveSpecialCharacter());

   89         _specifications.Add(new PasswordMustHaveAtLeastEightCharacters());

   90     }

   91     public bool IsSatisfiedBy(User candidate)

   92     {

   93         try

   94         {

   95             foreach (var specification in _specifications)

   96                 if (!specification.IsSatisfiedBy(candidate))

   97                     return false;

   98         }

   99         catch (Exception) { }

  100         return true;

  101     }

  102 }

Klassen arver fra Specification (for User) – et enkelt interface som bare inneholder én metode: IsSatisfiedBy(T candidate). Hvis IsSatifiedBy returnerer true for en gitt bruker, så kan registreringen gjennomføres.

ValidRegistrationSpecification er videre egentlig en liste med med mer detaljerte spesifikasjoner, én for hver test jeg har skrevet, og IsSatifiedBy spoler gjennom alle disse. La oss se nærmere på én av dem, som spesifiserer at et passord må inneholde bokstaver:

   19 public class PasswordMustHaveLetter : Specification<User>

   20 {

   21     public bool IsSatisfiedBy(User candidate)

   22     {

   23         for (int i = 0; i < candidate.Password.Length; i++)

   24             if (Char.IsLetter(candidate.Password[i]))

   25                 return true;

   26         return false;

   27     }

   28 }

Du er kanskje ikke vandt til klasser på bare 10 linjer (inkludert klammene)? Det er ingenting galt i det skal jeg si deg.

Her følger den mest avanserte spesifikasjonen (med en Cyclomatic Complexity på hele 3) som kontrollerer at brukernavnet ikke er opptatt. Den er avhengig av en UserRepository som den benytter til å se om den finner en match på brukernavnet.

   64 public class UsernameMustBeAvailable : Specification<User>

   65 {

   66     private UserRepository _repository;

   67     public UsernameMustBeAvailable(UserRepository repository)

   68     {

   69         _repository = repository;

   70     }

   71     public bool IsSatisfiedBy(User candidate)

   72     {

   73         return _repository.Find((user)

   74             => user.Username == candidate.Username)

   75             .Count() == 0;

   76     }

   77 }

Jeg har ikke lest Eric Evans bok, så jeg vet ikke om denne løsningen er helt i tråd med Domain-Driven Design. Men jeg vet han spesifikt snakker om Specification pattern (excuse the pun). Og i sommer lærte Bellware meg at entiteter aldri skal referere domenetjenester (som repositories), så valideringen kunne ikke vært plassert i User-klassen.

For ordens skyld – jeg sier ikke at det jeg driver med er Domain-Driven Design. Jeg vil heller, som Bellware, kalle det DDD-light: Jeg bruker mønstrene og ideene fra DDD der det gir verdi og mening i det systemet jeg til enhver tid utvikler.

I neste blogpost skal jeg samle trådene fra utenfra-og-inn programmering, TDD og mocking i praksis og denne posten, og analysere designet jeg har kommet opp med.

Knagger: , , , , , , ,

TDD og mocking i praksis

I de to forrige postene, Utenfra-og-inn programmering og Avhengigetsisolering (a.k.a Mocking), har jeg forklart hvordan jeg praktiserer testdreven utvikling. Denne gangen gjør jeg et forsøk på å vise det i praksis, en slags “tutorial” om du vil. Det er selvfølgelig en overhengende fare for at jeg produserer en svært lang blogpost, men om du har lyst til å bli med meg og oppleve Mockist TDD i praksis så er du i alle fall svært velkommen.

Jeg dokumenterer steg for steg hva jeg gjør, og om du vil må du gjerne kode oppgaven parallelt med meg – det vil du nok lære mest av. I så fall trenger du NUnit og Moq (eller annet testrammeverk og mockingrammeverk du er komfortabel med), db4objects, samt Visual Studio. Jeg har ikke lagt ved den ferdige løsningen, det er selve prosessen som er poenget her.

Når jeg gjør testdreven utvikling forsøker jeg å utvikle i så små steg som mulig, og jeg hopper mye frem og tilbake mellom test og produksjonskode. Jeg har også en tendens til å foretrekke mange, små klasser som jobber sammen for å løse oppgaver, til fordel for større klasser med mer kompleks logikk. Noen vil kalle det overkill, jeg kaller det et fleksibelt design. Du kan gjøre deg opp din egen mening.

Oppgaven

“På vår fantastiske webside skal man kunne registrere seg som ny bruker med brukernavn og passord. Passordet skal selvsagt lagres kryptert. Når man har en brukerkonto skal man kunne logge inn.”

Ok, det hørtes ikke så vanskelig ut – de fleste utviklere har gjort dette noen ganger før. Asp.net har jo også kontroller og providere som gjør dette veldig enkelt. Men vi skal bruke denne oppgaven til å øve på TDD, så vi later som om alt det der ikke finnes, glemmer hva vi har gjort tidligere, og setter i gang.

En liten påminnelse: Det finnes hundrevis av måter å implementere dette på, og det finnes hundrevis av måter å gjøre TDD på. Dette er min fremgangsmåte, slik jeg foretrekker akkurat nå. Det finne singen fasit, og neste gang ville jeg kanskje ha gjort det anderledes. Så ikke kopier meg, men reflekter over hvorfor jeg gjør det jeg gjør, og du vil sikkert lære noe på veien.

Forberedelser

Jeg fyrer opp Visual Studio og åpner et nytt prosjekt basert på Asp.net Web Application templaten. Vi innbiller oss at dette er den eksisterende websiten vi skal utvikle den nye “featuren” for. For å gjøre dette enkelt vil alt opprettes i dette ene prosjektet, inkludert testene – normalt ville jeg ha fordelt elementene i løsningen i diverse moduler.

Den første testen, en kontroller og et view

Oppgaven vi har fått er todelt: Brukerregistrering og brukerautentisering. Registrering kommer naturlig før autentisering, så vi starter med det. Er du en utvikler som ikke er vandt til TDD og utenfra-og-inn programmering kan det nå være fristene å tenke at du har lyst til å starte med å opprette en bruker-klasse, eller kanskje definere hvordan databasen skal se ut. Det skal vi ikke gjøre. Ditt første instinkt skal være å skrive en test, en test som passerer om brukerregistreringen fungerer.

La oss opprette en testklasse – jeg velger å gi den det merkelige navnet When_registering_a_new_user:

    9 [TestFixture]

   10 public class When_registering_a_new_user

   11 {

   12 }

Jeg ser for meg at jeg vil ha en eller annen form for controller som tar seg av registreringen. Controlleren kan kommunisere med et view hvor brukeren skal taste inn et brukernavn og passord og trigge registreringen. Når registreringen er gjennomført kan controlleren kommunisere dette tilbake via viewet. La meg forsøke å skrive en test:

    9 [TestFixture]

   10 public class When_registering_a_new_user

   11 {

   12     [Test]

   13     public void Should_report_registration_ok_in_view()

   14     {

   15         var view = new UserRegistrationView();

   16         var controller = new UserRegistrationController(view);

   17         view.TriggerRegistrationRequest(“Kong Håkon”, “1234″);

   18         Assert.IsTrue(view.RegistrationOk);

   19     }

   20 }

Ingen av disse klassene finnes enda. Dette er helt vanlig når man gjør TDD; man forsøker først å finne ut hvordan klassene bør se ut – deretter implementerer man dem.

Jeg er ikke helt fornøyd med hvordan jeg må trigge registration request ved å kalle en metode på viewet, og jeg like heller ikke at jeg må implementere to ulike klasser for denne testen – jeg ønsker å fokusere på én ting om gangen. Men hvis jeg foreløpig lar viewet være et abstrakt interface, og tar i bruk mockingrammeverket (Moq) kan vi få dette til å bli litt bedre. Jeg endrer testen:

   13 [Test]

   14 public void Should_report_registration_ok_in_view()

   15 {

   16     var mockView = new Mock<UserRegistrationView>();

   17     var controller = new UserRegistrationController(mockView.Object);

   18     mockView.Raise(view => view.RegistrationRequested += null, EventArgs.Empty);

   19     mockView.Verify(view => view.SetRegistrationResult(true), Times.Once());

   20 }

Det vil være mye rødt i editoren nå siden det fortsatt ikke er noen av disse klassene, metodene eller eventene som eksisterer, men det lærer vi oss raskt å leve med, så forsøk å se bort i fra det.

Denne testen synes jeg ble litt bedre. På linje 18 trigger jeg et RegistrationRequested event, som jeg tenker vil tilsvare eventet som fyres når brukeren klikker på “register” knappen på websiden. På neste linje verifiserer jeg at SetRegistrationResult har blitt kalt med argumentet “true”. Jeg vil nemlig at controlleren skal plukke opp registreringsforespørselen og kalle den metoden om alt går som det skal. I stedet for å verifisere på tilstand verifiserer jeg nå adferd.

Det er på tide å gjøre noe med alle de røde advarslene, så nå vil jeg opprette controlleren og viewet. Siden jeg bruker CodeRush er dette kun noen få tastetrykk – bruker du ikke noe slikt verktøy må du gjøre det for hånd. Dette er hva jeg kommer opp med.

   10 public interface UserRegistrationView

   11 {

   12     event EventHandler RegistrationRequested;

   13     void SetRegistrationResult(bool success);

   14 }

   15 public class UserRegistrationController

   16 {

   17     private UserRegistrationView _view;

   18     public UserRegistrationController(UserRegistrationView view)

   19     {

   20         _view = view;

   21     }

   22 }

Nå kompilerer prosjektet vårt, men kjører jeg testen vil den selvfølgelig feile, for SetRegistrationResult vil aldri bli kalt. Og det er helt perfekt. I TDD følger man et mønster som heter “Red, Green, Refactor”, som innebærer at vi først skal sørge for å lage en test som feiler. Det har vi oppnådd.

En grønn test

Så hvordan kan vi komme til neste steg – en vellykket test? Jo, det er veldig enkelt, selv om det virker som en helt idiotisk ting å gjøre:

   18 public UserRegistrationController(UserRegistrationView view)

   19 {

   20     _view = view;

   21     view.SetRegistrationResult(true);

   22 }

Jeg kaller rett og slett SetRegistrationResult i konstruktøren til controlleren. Hvorfor det? Jo, nå vet jeg at testen fungerer – at testen blir grønn hvis SetRegistrationResult kalles. To av tre steg er gjennomført, og vi kan gå igang med det litt vage steget “refakturering”.

Men hva med eventet som trigges? Det var jo det som skulle føre til registreringen. Stemmer det! Jeg kunne ha skrevet en ny test som passet på at SetRegistrationResult ikke ble kalt uten at eventet ble trigget, men akkurat nå synes jeg ikke det er nødvendig. Jeg gjør denne endringen med den ene testen jeg har:

   15 public class UserRegistrationController

   16 {

   17     private UserRegistrationView _view;

   18     public UserRegistrationController(UserRegistrationView view)

   19     {

   20         _view = view;

   21         _view.RegistrationRequested += _view_RegistrationRequested;

   22     }

   23 

   24     void _view_RegistrationRequested(object sender, EventArgs e)

   25     {

   26         _view.SetRegistrationResult(true);

   27     }

   28 }

.. jeg kjører testen på nytt etter endringen for å være sikker på at jeg ikke har ødelagt noe; den er fortsatt grønn, så alt er ok.

Test nummer 2: User repository

Men det skjer jo ingen registrering her. Hmmm. En registrering innebærer å opprette og lagre en ny bruker, ikke sant?! (Som du ser spiller jeg dum, og forplikter meg dermed ikke til et konkret design før det er absolutt nødvendig.) Ok, da trenger vi en eller annen form for Repository som kan lagre nye brukere. Jeg får skrive en ny test – kan jo ikke begynne å implementere noe nytt uten…

   40 [Test]

   41 public void Should_persist_user_in_repository()

   42 {

   43     var mockView = new Mock<UserRegistrationView>();

   44     var mockRepository = new Mock<UserRepository>();

   45     var controller = new UserRegistrationController(mockView.Object, mockRepository.Object);

   46     mockView.Raise(view => view.RegistrationRequested += null, EventArgs.Empty);

   47     mockRepository.Verify(r => r.Insert(It.IsAny<User>()));

   48 }

Jeg lager en ny mock som skal representere en repository, og sender den også inn til controlleren. I linje 47 kontrollerer jeg at Insert har blitt kalt, og at det ble sendt inn et User objekt. Hverken UserRepository eller User eksisterer enda.

Det meste av testen ligner på den forrige testen, så DRY-prinsippet (kanskje det aller viktigste prinsippet innen softwareutvikling) tvinger meg til å trekke ut det som er felles. Jeg ender opp med denne testklassen:

   29 [TestFixture]

   30 public class When_registering_a_new_user

   31 {

   32     [Test]

   33     public void Should_report_registration_ok_in_view()

   34     {

   35         When_doing_a_registration();

   36         mockView.Verify(view => view.SetRegistrationResult(true), Times.Once());

   37     }

   38     [Test]

   39     public void Should_persist_user_in_repository()

   40     {

   41         When_doing_a_registration();

   42         mockRepository.Verify(r => r.Insert(It.IsAny<User>()));

   43     }

   44     private void When_doing_a_registration()

   45     {

   46         mockView = new Mock<UserRegistrationView>();

   47         mockRepository = new Mock<UserRepository>();

   48         controller = new UserRegistrationController(mockView.Object, mockRepository.Object);

   49         mockView.Raise(view => view.RegistrationRequested += null, EventArgs.Empty);

   50     }

   51     private Mock<UserRepository> mockRepository;

   52     private Mock<UserRegistrationView> mockView;

   53     private UserRegistrationController controller;

   54 }

For at dette skal kompilere må jeg gjøre tre ting; opprette UserRepository, fikse controllerens konstruktør til å ta inn en repository, og opprette en User klasse. Noen CodeRush-shortcuts senere har jeg opprettet følgende:

   15 public class User

   16 {

   17 }

   18 public interface UserRepository

   19 {

   20     void Insert(User userToInsert);

   21 }

   22 public class UserRegistrationController

   23 {

   24     private UserRepository _repository;

   25     private UserRegistrationView _view;

   26     public UserRegistrationController(

   27         UserRegistrationView view, UserRepository repository)

   28     {

   29         _repository = repository;

   30         _view = view;

   31         _view.RegistrationRequested += _view_RegistrationRequested;

   32     }

Prosjektet kompilerer igjen. Vår første test passerer fortsatt, men den nye testen gjør ikke det, så det får vi gjøre noe med. Vi må rett og slett sørge for at den nye brukeren lagres:

   33 private void _view_RegistrationRequested(object sender, EventArgs e)

   34 {

   35     _repository.Insert(new User());

   36     _view.SetRegistrationResult(true);

   37 }

Der, nå er begge testene grønne. Kontrolleren lagrer en ny bruker til repositorien, og sier fra til viewet at det er gjort. Men den mangler noe veldig sentralt. Trenger vi ikke noe mer input? Hvor er brukernavnet og passordet?

Har du noen gang programmert så mye adferd uten å lage en eneste public property? Er det ikke interessant hvordan TDD lar oss fokusere på adferd? Men nå er det kanskje på tide å legge til i alle fall brukernavn og passord. Men først må vi ha en test for dette. Den siste testen vi skrev verifiserer at brukeren lagres til basen. Hva om vi legger til å validere selve brukeren som legges til også – at den har riktig navn f.eks.? Jeg forsøker meg på dette ved å endre testen til dette her:

   48 [Test]

   49 public void Should_persist_user_in_repository()

   50 {

   51     User addedUser = null;

   52     mockRepository

   53         .Setup(r => r.Insert(It.IsAny<User>()))

   54         .Callback((User user) => addedUser = user);

   55     When_doing_a_registration();

   56     Assert.AreEqual(“Kong Håkon”, addedUser.Username);

   57 }

Jeg bruker litt Moq-magic til å sette opp en callback som tar vare på brukeren som sendes til repositorien, og sjekker at navnet på den er “Kong Håkon”. Men hvor har jeg fått dette navnet fra? Det skal jo brukeren sende inn.., via viewet selvfølgelig. Så vi setter opp dette også:

   50 [Test]

   51 public void Should_persist_user_in_repository()

   52 {

   53     mockView.SetupGet(v => v.Username).Returns(“Kong Håkon”);

   54     User addedUser = null;

   55     mockRepository

   56         .Setup(r => r.Insert(It.IsAny<User>()))

   57         .Callback((User user) => addedUser = user);

   58     When_doing_a_registration();

   59     Assert.AreEqual(“Kong Håkon”, addedUser.Username);

   60 }

Jeg forventer nå at viewet har en Username property, og at den vil returnere “Kong Håkon” for oss når den blir forespurt. Registreringsjobben skal da overføre dette navnet til brukeren som lagres. Peace of cake! Jeg oppretter de to propertiene jeg trenger for å få kompilert, kjører testen én gang for å se det røde lyset, implementerer litt mer logikk i kontrolleren, og har to grønne tester igjen.

PS: Jeg har forøvrig trukket ut litt logikk fra When_doing_a_registration() og plassert det i en SetUp-metode – hvis ikke hadde jeg fått en null-ref exception i linje 53.

   10 public interface UserRegistrationView

   11 {

   12     event EventHandler RegistrationRequested;

   13     string Username { get; }

   14     void SetRegistrationResult(bool success);

   15 }

   16 public class User

   17 {

   18     public string Username { get; set; }

   19 }

   35 private void _view_RegistrationRequested(object sender, EventArgs e)

   36 {

   37     _repository.Insert(new User

   38     {

   39         Username = _view.Username

   40     });

   41     _view.SetRegistrationResult(true);

   42 }

Passordkryptering

Nå kunne jeg gjort det samme for passord som for brukernavn, men oppgaven sa at jeg skulle kryptere passordet. Det er derfor viktig at passordet som lagres IKKE er det samme som sendes inn fra viewet. Jeg jobber litt til med testene:

   63 Assert.AreNotEqual(“passw0rd”, addedUser.Password);

Nei, dette føles ikke særlig riktig. Når jeg tenker over det er hashing av passord et helt annet ansvar, noe kontrolleren kan deligere til noen andre. Ved å utnytte det kan jeg skrive en enkel test som validerer at passordet hashes, mens jeg utsetter selve hash-logikken.

   77 [Test]

   78 public void Should_hash_password()

   79 {

   80     When_doing_a_registration();

   81     mockHasher.Verify(h => h.HashPassword(It.IsAny<string>()));

   82 }

Jeg har begynt å gå litt raskere frem nå, men jeg regner med at du klarer å henge med. Jeg har lagt til en ny avhengighet, PasswordHashService, som jeg injecter i controller-konstruktøren. Og så skriver jeg en ny test som verifiserer at HashPassword kalles. Noen sekunder senere får jeg dette til å kompilere og testen til å passere ved å gjøre følgende..

   39 private void _view_RegistrationRequested(object sender, EventArgs e)

   40 {

   41     _repository.Insert(new User

   42     {

   43         Username = _view.Username,

   44         Password = _hasher.HashPassword(_view.Password),

   45     });

   46     _view.SetRegistrationResult(true);

   47 }

Password settes i linje 44. Jeg bruker to ulike properties som ikke finnes enda.., så jeg oppretter dem. Done!

En konkret hasher

Vi har nå laget en fungerende controller, men ingen av de objektene den sammarbeider med eksisterer enda – de ble alle “mocket ut” i testene. Vi kan nå jobbe oss innover. Jeg velger å ta PasswordHashService først. Jeg skriver selvsagt tester før produksjonskode.., og her er den første:

   55 [TestFixture]

   56 public class MD5HasherTests

   57 {

   58     [Test]

   59     public void QuickBrownFoxTest()

   60     {

   61         Assert.AreEqual(

   62             “9e107d9d372bb6826bd81d3542a419d6″,

   63             new MD5Hasher().HashPassword(“The quick brown fox jumps over the lazy dog”));

   64     }

   65 }

Jeg fant hashen til denne setningen i Wikipedia’s artikkel om MD5, så jeg tenkte den kunne være et greit utgangspunkt for å teste algoritmen. Litt cut n’ paste fra nettet og prøving og feiling gir meg følgende implementasjon som passerer testen:

   53 public class MD5Hasher : PasswordHashService

   54 {

   55     public string HashPassword(string passwordToHash)

   56     {

   57         var passwordBytes = Encoding.UTF8.GetBytes(passwordToHash);

   58         var hashBytes = MD5.Create().ComputeHash(passwordBytes);

   59         var buffer = new StringBuilder();

   60         for (int i = 0; i < hashBytes.Length; i++)

   61             buffer.Append(hashBytes[i].ToString(“x2″).ToLower());

   62         return buffer.ToString();

   63     }

   64 }

En konkret repository

Neste interface jeg trenger en konkret implementasjon av er UserRepository. For å implementere den trenger jeg et persisteringsmedium, en eller annen form for database. Det enkleste jeg vet om er å bruke en objektdatabase, db4objects, så det vil jeg gjøre her. Jeg kan enkelt sette opp en test som oppretter en database, lagrer en bruker, kontrollerer at jeg kan hente brukeren ut igjen, og rydder opp ved å slette basen. Jeg gjør dette i små steg, men her er alt i en batch:

   44 [TestFixture]

   45 public class Db4oUserRepositoryTests

   46 {

   47     const string db_path = @”c:\temp\Db4oUserRepositoryTests.dbo”;

   48     private IObjectContainer database;

   49     private Db4oUserRepository repository;

   50 

   51     [SetUp]

   52     public void SetUp()

   53     {

   54         database = Db4oFactory.OpenFile(db_path);

   55         repository = new Db4oUserRepository(database);

   56     }

   57     [TearDown]

   58     public void TearDown()

   59     {

   60         database.Dispose();

   61         File.Delete(db_path);

   62     }

   63     [Test]

   64     public void Should_insert_user()

   65     {

   66         repository.Insert(new User { Username = “Foo” });

   67         Assert.AreEqual(

   68             “Foo”,

   69             (from User u in database select u).Single().Username);

   70     }

   71 }

Jeg vil ha en konkret UserRepository som jeg vil kalle Db4oUserRepository. Jeg vil sende en instans av en objektdatabase inn via konstruktøren (IObjectContainer er et db4objects interface). I setup-metoden oppretter jeg en ny filbasert database og selve repositorien, og i teardown disposer jeg databasen og sletter filen.

Selve testen lagrer en ny bruker i repositorien, og så bruker jeg Linq 2 Db4o for å hente ut alle brukere fra basen, sjekke at det finnes én-og-bare-én bruker (ved å kalle Single()), og kontrollere at brukernavnet er riktig. Vær oppmerksom på at du må legge til “using Db4objects.Db4o.Linq” for å få Linq-spørringen i linje 69 til å kompilere. Vil du vite mer om Db4objects kan du lese min blogpost om dette fantastiske rammeverket.

Følgende enkle implementasjon tilfredstiller testen over:

   27 public class Db4oUserRepository : UserRepository

   28 {

   29     private IObjectContainer _database;

   30     public Db4oUserRepository(IObjectContainer database)

   31     {

   32         _database = database;           

   33     }

   34     public void Insert(User userToInsert)

   35     {

   36         _database.Store(userToInsert);

   37     }

   38 }

Et konkret view

Det eneste som gjenstår nå for å ha en fungerende brukerregistrering er å lage et konkret view. Dette er den eneste biten jeg ikke bryr meg om å skrive tester for – det er somregel ikke verdt innsatsen, sålenge forretningslogikken er separert ut av viewet. Jeg oppretter en ny Web User Control, kaller den Registration.ascx, og dropper inn to TextBox’er og en Button.

    1 <%@ Control Language=”C#” AutoEventWireup=”true” CodeBehind=”Registration.ascx.cs”

    2     Inherits=”WebApplication1.Registration” %>

    3 User name: <asp:TextBox ID=”txtUsername” runat=”server”></asp:TextBox><br />

    4 Password: <asp:TextBox ID=”txtPassword” runat=”server”></asp:TextBox><br />

    5 <asp:Button ID=”createUserButton” runat=”server” Text=”Create user”

    6     onclick=”createUserButton_Click” />

Og her er code-behind, hvor jeg lar Registration arve fra UserRegistrationView:

   11 public partial class Registration : UserControl, UserRegistrationView

   12 {

   13     private UserRegistrationController _controller;

   14     protected void Page_Load(object sender, EventArgs e)

   15     {

   16         _controller = new UserRegistrationController(

   17             this,

   18             Application["UserRespoitory"] as UserRepository,

   19             new MD5Hasher());

   20     }

   21 

   22     // Inherited from UserRegistrationView

   23     public event EventHandler RegistrationRequested;

   24     public string Username { get { return txtUsername.Text; } }

   25     public string Password { get { return txtPassword.Text; } }

   26     public void SetRegistrationResult(bool success)

   27     {

   28         if (success)

   29         {

   30             createUserButton.Text = “Success!”;

   31             createUserButton.Enabled = false;

   32         }

   33     }

   34     // end UserRegistrationView Members

   35     protected void createUserButton_Click(object sender, EventArgs e)

   36     {

   37         if (RegistrationRequested != null)

   38             RegistrationRequested(this, e);

   39     }

   40 }

Denne approchen er opprinnelig inspiret av Phil Haack’s blogpost om Supervising Controller i ASP.NET. Initaliseringen av kontrolleren som foregår i Page_Load knytter sammen alle objektene som trengs for å registrere en bruker, og når brukeren fyrer av RegistrationRequested eventet ved å klikke på knappen vil controlleren ta over og gjennomføre registreringen.

Om du drar inn Registration-kontrollen i Default.aspx så kan du teste at det fungerer som det skal. Foreløpig får du tilbakemelding ved at knappen blir disablet og teksten sier “Success!”, men her kan man tenke seg at vi skjuler hele kontrollen og viser login-kontrollen (som fortsatt gjenstår å lage) i stedet.

Jeg har valgt å plassere repositorien i Application state – jeg vet ikke hvor bra dette fungerer i praksis, og det er ikke sånn jeg ville har gjort det “i virkeligheten”, men det fungerer greit her og nå. ASP.NET og state er ikke det viktige temaet i denne artikkelen, så ikke gi meg flak for denne fremgangsmåten. Initaliseringen skjer i Global.asax:

    6 public class Global : System.Web.HttpApplication

    7 {

    8     protected void Application_Start(object sender, EventArgs e)

    9     {

   10         var database = Db4oFactory.OpenFile(@”c:\temp\Db4oUserRepository.dbo”);

   11         Application["Db4oUserRepository.dbo"] = database;

   12         Application["UserRespoitory"] = new Db4oUserRepository(database);

   13     }

   14 

   15     protected void Application_End(object sender, EventArgs e)

   16     {

   17         var database = Application["Db4oUserRepository.dbo"] as IDisposable;

   18         database.Dispose();

   19     }

   20 }

Innlogging

For å implementere innlogging går jeg frem som jeg gjorde da jeg startet på brukerregistrering. Innlogging er et eget ansvar som trenger egne tester, eget view og egen controller. Controlleren vil bruke UserRepository, som man gjennom testene må utvide med logikk for å hente opp en bruker basert på navn. Den vil også gjenbruke PasswordHashService, fordi passordet må hashes før man kan sammenligne med det lagrede passordet.

Gjennom denne blogposten har du allerede sett og lært alt du trenger for å fullføre oppgaven, så jeg stopper her. Om du har lyst til å øve deg litt selv så anbefaler jeg at du fortsetter på egenhånd. Skriv tester først, og ikke lag en eneste klasse eller metode før du har kode (test eller annen kode) som allerede refererer klassen/metoden.

Jeg håper du likte denne TDD-tutorialen. I en ny blogpost som kommer veldig snart vil jeg analysere designet jeg har kommet opp med, og snakke litt om best practises rundt smidig arkitektur. På gjensyn!

Knagger: , , , , , , , , , ,

Avhengighetsisolering (a.k.a. Mocking) i .NET

mocking_frameworks I min forrige blogpost, om utenfra-og-inn programmering, snakket jeg om at mocking er en teknikk vi bruker når vi praktiserer testdreven utvikling. For å kunne utvikle én og én enhet om gangen er det fordelaktig å kunne erstatte enhetens avhengigheter med “jukseobjekter”.

Roy_Pic_BW_Small Enhetstest-guru Roy Osherove (som jobber for TypeMock) påpeker at vi ofte misbruker begrepet mocking. Martin Fowler har også skrevet en mye lest artikkel kalt Mocks Aren’t Stubs om dette: En MOCK er ifølge dem et objekt som brukes til å registrere og validere forventninger, mens en STUB er et objekt som inneholer predefinerte svar. FAKE er et objekt med en faktisk implementasjon, som for eksempel en in-memory database som erstatter en faktisk database. Vi bruker normalt en blanding av disse i testene våre, og jeg ser ikke noen spesiell grunn til å kunne skille mellom dem.

Roy anbefaler å bruke begrepet avhengighetsisolering for å samle disse teknikkene, og kaller TypeMock for et avhengighetsisoleringsrammeverk (det ble veldig fint på norsk gitt). Jeg bruker likevel mocking-begrepet, siden det er mest kjent – alle forstår hva jeg mener da.

Uansett hva man kaller dem – fordelen med disse falske objektene er at de er til å stole på. Mens faktiske implementasjoner kan gi ulike svar (basert på eksterne data, randomness etc.) kan vi fullstendig kontrollere hvordan en fake skal oppføre seg i enhver situasjon. For eksempel kan vi simulere ulike unntasksituasjoner ved å få dem til å kaste exceptions på kommando.

Testene blir i mange tilfeller også enklere å skrive, gitt at du behersker et godt mockingrammeverk. Testene kan dessuten eksekvere raskere, fordi avhengighetene blir “lettere” enn de faktiske avhengighetene.

Litt kontroverielt

Jeg føler meg forpliktet til å nevne at det er en viss diskusjon rundt bruk av mocking. Det er mange som hevder at mocking er unødvendig, og kanskje til og med uønsket, om man designer systemet sitt “riktig”. Jeg var selv av den oppfatning for ikke så alt for lenge siden. Men etter å ha forsøkt det en stund endret jeg mening.

Likevel innser jeg at mocking kan missbrukes – spesielt de kraftigste rammeverkene. Når man skriver tester er det viktig å teste adferd, ikke implementasjon – knytter man dem for hardt mot implementasjonen blir testene sårbare for endringer i systemet. Du kan få en grei oversikt over denne debatten ved for eksempel å lese et par sentrale spørsmål på stackoverflow: Why do I need a mocking framework for my unittests? | The value of high level unit tests and mock objects | Should one test internal implementation, or only test public behaviour?

Resten av denne blogposten er ment å gi en oversikt over hvilke muligheter du som utvikler på .NET-plattformen har for å praktisere avhengighetsisolering.

Manuell mocking

La oss først se på hvordan manuell gjør-det-selv-mocking fungerer. Det kan være lurt å starte der, for å få en grunnleggende forståelse for hvordan og hvorfor man mocker. La oss si at jeg skal teste en LoginController-klasse. Den har to avhengigheter; et LoginView og en UserRepository.

    1 [Test]

    2 public void Should_login_user_successfully()

    3 {

    4     var view = new FakeLoginView();

    5     new LoginController(view, new FakeUserRepository());

    6     view.TriggerAuthenticationRequested();

    7     Assert.IsTrue(view.hidden);

    8     Assert.IsTrue(view.Result == AuthenticationResult.Ok);

    9 }

Etter best practises injectes avhengighetene inn gjennom konstruktøren til kontrolleren – dette gjør det mulig for oss å mocke avhengighetene gjennom arv. I testen oppretter jeg stubs som jeg bruker til å simulere at en bruker trigger en authentiserings-request, og til å sjekke at resultatet av authentiseringen er at viewet skjules og Result-propertien settes til OK.

De to stub-klassene, som jeg koder spesifikt for denne testen, ser slik ut:

    1 class FakeLoginView : LoginView

    2 {

    3     // LoginView Members

    4     public event EventHandler AuthenticationRequested;

    5     public string Username { get { return “torbjorn”; } }

    6     public string Password { get { return “passw0rd”; } }

    7     public AuthenticationResult Result { get; set; }

    8     public void Hide() { hidden = true; }

    9     // end LoginView Members

   10 

   11     public FakeLoginView() { Result = AuthenticationResult.Pending; }

   12     public virtual void TriggerAuthenticationRequested()

   13     {

   14         if (AuthenticationRequested != null)

   15             AuthenticationRequested(null, EventArgs.Empty);

   16     }

   17     public bool hidden;

   18 }

   19 

   20 class FakeUserRepository : UserRepository

   21 {

   22     public User FindByName(string username)

   23     {

   24         return new User {

   25             PasswordHash = “BED128365216C019988915ED3ADD75FB” };

   26     }

   27 }

Stub’ene er spesialdesignet for å gi den adferden jeg trenger i testen; FakeLoginView returnerer et predefinert brukenavn og passord, og har en variabel jeg kan sjekke mot for å finne ut om Hide-metoden har blitt kalt. FakeUserRepository vil uansett input returnere en bruker med et passord-hash som korresponderer med det predefinerte passordet.

Det er endel arbeid å lage disse stub’ene, men det er ikke så vanskelig. Problemet er at slik de er nå er de bare anvendelige for et fåtall tester – for andre tester som skal simulere andre ting må jeg skrive nye stubs (eller utvide disse med mer logikk). Jo flere stubs man må lage, jo mer kompleksitet innfører man i test-suiten.

Moq

Moq er et relativt nytt, open source mockingrammeverk for .NET, utviklet av Daniel Cazzulino (@kzu). Rammeverket er designet med tanke på enkelhet, er basert på Castle DynamicProxy, og er min klare favoritt. Jeg har nylig innført Moq på jobben, og det virket ikke som om utviklerne hadde store problemer med å ta det i bruk.

Moq bruker ikke Record/Replay-semantikken som flere andre rammeverk er kjent for, det har en mere intuitiv Arrange-Act-Assert approach. Eneste potensielle ulempen med rammeverket er at du må bruke .NET 3.5 (eller høyere).

Tilsvarende test som den over blir som følger ved bruk av moq:

    1 [Test]

    2 public void Should_login_user_successfully()

    3 {

    4     var view = new Mock<LoginView>();

    5     view

    6         .Setup(v => v.Password)

    7         .Returns(“passw0rd”);

    8 

    9     var repository = new Mock<UserRepository>();

   10     repository

   11         .Setup(r => r.FindByName(It.IsAny<string>()))

   12         .Returns(new User { PasswordHash = “BED128365216C019988915ED3ADD75FB” });

   13 

   14     new LoginController(view.Object, repository.Object);

   15     view.Raise(v => v.AuthenticationRequested += null, EventArgs.Empty);

   16     view.Verify(v => v.Hide());

   17     view.VerifySet(v => v.Result = AuthenticationResult.Ok);

   18 }

I linje 4 til 12 setter jeg opp mockene (Arrange): Jeg lager et view som skal returnere et predefinert passord, og en repository som skal returnere et predefinert passord-hash. I linje 14 sender jeg inn mockene til controlleren, og i linje 15 trigger jeg autentiseringforespørselen (Act). I linje 16 verifiserer jeg at Hide-metoden har blitt kalt, og linje 17 sjekker at Result har blitt satt til OK (Assert).

Moq er under stadig utvikling, og virker ganske populært for tiden. Daniel lanserte for eksempel nettopp Linq to Mocks – en helt ny approach for å opprette mock-objekter i form av en spørring. Det eksisterer også et moq-contrib prosjekt hvor du finner støtte for Automocking (rekursiv oppretting av mocks – innebærer mindre Arrange-kode i testene). OPPDATERING: Rekursiv mocking har blitt flyttet fra moq-contrib til å bli del av moq i versjon 2.6.

TypeMock Isolator

TypeMock Isolator er det aller kraftigste mockingrammeverket vi har tilgjengelig – det kan gjøre ting ingen av de andre kan, som å mocke ikke-virtuelle metoder, statiske klasser og klasser merket som sealed. Ulempen er at det koster penger.., Ganske Mange Penger!

Mange hevder også at styrken dette rammeverket tilbyr lett kan føre til dårligere design, og det er jeg enig i. Vi tok i bruk TypeMock i min forrige jobb, og der førte det til at vi lagde dårlige og komplekse tester som var mer eller mindre umulige å vedlikeholde. Når man derimot allerede har et dårlig design som utgangspunkt – kanskje man har behov for å mocke deler av selve .net-rammeverket – så kan nok TypeMock være verdt sin vekt i gull. (Men hvor mye veier software egentlig?)

Rhino Mocks

Rhino Mocks er et open source rammeverk utviklet av Oren Eini (også kalt Ayende Rahien). Mens TypeMock er det kommersielle storebror med de kraftigste featurene, er Rhino Mocks det mest brukte mockingrammeverket blant .NET-utviklere. Tidligere hadde Rhino en lite intuitiv Record/Replay-syntax, men støtter nå Arrange-Act-Assert, og da ligner det i grunnen svært mye på Moq.

Rhino Mocks må sies å være et svært modent og fleksibelt rammeverk, og som Moq bygger det på Castle DynamicProxy. Den store fleksibiliteten kan være forvirrende, og dokumentasjonen kan være mangelfull. Dette veies opp ved at det har svært mange brukere, så noen andre har alltid gjort det du skal gjøre, og sansynligvis blogget om det.

Flere har utvidet Rhino Mocks til også å støtte automocking, bl.a. Jeremy D. Miller i sitt StructureMap-prosjekt. For det jeg vet kan det hende Ayende også har implementert dette i Rhino Mocks direkte by now.., man vet aldri hva den fyren får til på en ettermiddag.

Stubs

Microsoft Research har sluppet et rammeverk de kaller Stubs, som ble utviklet for deres nye testautomatiserings-verktøy Pex. Dette skiller seg ut fra de andre ved at det bruker kodegenerering. Det er alltid interessant å se hva som kommer ut av MS Research, og selv om jeg ikke vet om dette er interessant som en erstatning for Rhino eller Moq, så er det i alle fall en ny og spennende approach. Sjekk ut Getting Started with Stubs for en lynrask introduksjon.

Merk forøvrig at current versjonsnummer er 0.15.40714.1, så jeg vet ikke om dette er “produksjonsklart” for å si det sånn :)

NMock / Nmock2

Så vidt jeg vet var NMock det første mockingrammeverket på .NET plattformen, laget som en port av DynaMock fra Java. Versjon 2 av NMock ble utviklet av et annet team, og er implementert etter designet fra jMock. Interessen for disse rammeverkene har vært dalende, og det har flere svakheter i forhold til de mer moderne konkurrentene: Den viktigste forskjellen er at mockene ikke er typet ved compile time – man bruker “magiske strenger” for referanser, noe som kan være skummelt og vanskelig å vedlikeholde når ting i designet endres.

Er du interessert i å se nærmere på forskjellene på de fem rammeverkene jeg har nevnt sålangt – moq, TypeMock Isolator, Rhino Mocks, Stubs og NMock2 – så finnes det et prosjekt på Google Code du kan laste ned som heter Mocking Frameworks Compare. Her har du eksempeltester implementert vha. alle rammeverkene, i tillegg til en performance test som sammenligner dem.

Andre rammeverk

EasyMock er et mockingrammeverk fra Java, men det har blitt portet til .NET-plattformen under navnet EasyMock.NET. Det er egentlig alt jeg vet om dette rammeverket, jeg har ikke hørt om noen som bruker det, og det virker som om det ikke har blitt videreutviklet siden 2004, så det er neppe noe å satse på.

NUnit, “alles favorittrammeverk” for enhetstester, har også støtte for mocking – i NUnit-nedlastingen finner du nemlig en lite kjent dll som heter NUnit.Mocks. Jeg har lest at den har begrensede muligheter i forhold til andre rammeverk, men den kan kanskje være aktuell om man ikke skal ta i bruk avhengighetsisolering fullt ut, men har bruk for det i et par tilfeller, og i tillegg allerede bruker NUnit.

Så det var min oversikt over mockingrammeverk i .NET. Min neste blogpost er en praktisk toturial i utenfra-og-inn programmering hvor jeg kommer til å bruke mye mocking.., så følg med om du er interessert i det!

Til slutt: Har du erfaring med noen av disse rammeverkene er det veldig hyggelig om du legger igjen en kommentar med eventuelle mangler i blogposten. Jeg har ikke brukt alle disse vektøyene selv, og har ikke vært en aktiv “mocker” så veldig lenge. Sett meg på plass om du kan!

Knagger: , , , ,

Utenfra-og-inn programmering

Outside-in development, eller utenfra-og-inn programmering som jeg kaller det, er et av elementene i Dan Norths Behavior-Driven Development (BDD). Mens testdreven utvikling (TDD) i bunn og grunn bare betyr at du skal skrive tester før du skriver kode, forteller BDD gjennom utenfra-og-inn prinsippet hvordan du bør designe systemet ditt ved hjelp av testdreven utvikling (eller Code-by-Example, som Dan kaller det).

Prinsippet er ganske enkelt: Du begynner med det du vet at du trenger – det kravspesifikasjonen eller User Storien sier at du skal lage – adferden du skal produsere. For eksempel blir du bedt om å lage en webside hvor man kan administrere ett eller annet.., la oss si at vi skal administrere lagerbeholdningen i en butikk. Ok, da begynner vi ytterst med brukergrensesnittet.

Vi definerer da gjerne et View, som har ansvar for å vise lagerbeholdningen, og la brukeren gjøre operasjoner mot denne (legge til/slette/etc). Og så arbeider vi oss innover. Vi lager typisk en controller/presenter som har som oppgave å populere viewet med data og håndtere kommandoer fra brukeren via viewet. Når vi har den på plass ser vi at vi trenger mer – data fra en repository f.eks., så da lager vi den.

Gjennom denne prosessen oppdager vi de objektene brukergrensesnittet trenger å sammarbeide med, de objektene vi trenger på neste nivå, de objektene vi trenger på nivået etter det – slik fortsetter det til vi er i mål og alt fungerer. På denne måten følger vi YAGNI-prinsippet (You Ain’t Gonna Need It), fordi all koden vi skriver enten brukes av brukeren direkte (viewet i dette tilfellet), eller av annen kode som allerede er skrevet. Alt vi gjør gir dermed verdi her og nå.

rect3489

En historie fra virkeligheten..

Kontrasten til denne fremgangsmåten er typisk at vi begynner med en datamodell, gjerne i form av en relasjonsdatabase, og arbeider oss utover. Vi tror vi vet hva vi trenger i neste ledd, og lager en modell vi håper passer. Etterhvert som vi arbeider oss utover i lagene må vi hele tiden tilpasse oss det som ligger innenfor.

I forrige uke skulle vi løse en oppgave hvor vi skulle ta imot en innkommende SMS-melding, ta noen avgjørelser basert på meldingens innhold og tidligere hendelser i systemet, og sende ut en ny SMS.

Jeg brukte TDD, og begynte med å lage en “meldingshåndterer” som tok imot SMS’en. Jeg jobbet meg innover og oppdaget andre ansvar som håndtereren trengte; en klasse for å lese meldingsinnholdet, en klasse for å ta avgjørelser basert på innholdet, en klasse jeg kunne bruke for å spørre om tidligere hendelser, en klasse som kunne konstruere nye meldinger etc.

Min kollega begynte i andre enden. Han definerte et par databasetabeller, et par entitetsklasser, og laget en repository med metoder for CRUD-operasjoner mot tabellene og mapping mot entitetene. Da vi etter ikke alt for lang tid sammenlignet hva vi hadde gjort, så vi raskt at det var en klar mismatch. Logikken jeg hadde laget hadde ikke noe behov for alle CRUD-operasjonene min kollega hadde laget – jeg trengte bare noen få og dessuten mye enklere metoder. Og entitetsklassene ble forkastet. Utenfra-og-inn hadde hjulpet meg til å lage et design som var mer fokusert på adferden vi skulle levere. Modellen man tror man trenger når man tenker innenfra-og-utover stemmer ofte ikke med det designet testdreven utvikling gir deg.

I ALT.NET miljøet sier man ofte at du kan/bør designe hele systemet ditt før du oppretter databasen. Databasen er der bare for å persistere tilstand, eller ta vare på historikk for rapportering. Den er ikke essensiell for programvaren. De bruker uttrykket persistence ignorance – forretningslogikken, kjernen i programvaren, skal ikke bry seg om hvordan data lagres. Dette står i sterk kontrast til hvordan de fleste utviklingsprosjekter jeg har vært borti foregår, hvor det første man gjør når man skal begynne på en ny feature er å definere hvordan tabellen skal se ut.

“Mocking”

mockery For å gjøre utenfra-og-inn programmering på en effektiv måte må man gå i små steg, og få tilbakemelding hele tiden gjennom testene man skriver. Hvis man skal utvikle alle klassene man trenger for å tilfredstille en feature før man kan sjekke om den fungerer som forventet, gjør man det ekstremt vanskelig for seg selv. Det er her mocking kommer inn i bildet.

(Illustrasjonen til høyre er hentet fra Michael Feathers og Steve Freemans foredrag Test Driven Development: Ten Years Later. Anbefales som en historisk oppsummering av TDD.)

Det vi populært kaller mocking betyr at når vi utvikler en betsemt enhet/klasse/modul så isolerer vi oss bort fra avhengighetene til denne enheten/klassen/modulen. I eksempelet mitt hvor jeg skal lage et webgrensesnitt for å administrere lagerbeholdning vil jeg ikke tenke på repository-klassen når jeg utvikler controlleren. Men controlleren trenger helt klart en repository. Vi lager da en falsk repository som controlleren kan bruke i testene vi skriver.

Når vi er ferdig med controlleren, og skal utvikle repository-klassen, er det repositoriens avhengigheter vi “mocker ut”. Følger man dette helt strickt, vil en feil i en testklasse alltid korrespondere til en feil i den éne testede klassen. Klassen som testes/utvikles isoleres fra resten av systemet.

Jeg praktiserte lenge TDD helt uten mocking, og klarte ikke se verdien av å begynne med det. Jeg trodde mocking var noe man gjorde om man ikke klarte å gjøre TDD på en orntlig måte. Det var først når jeg begynte å følge Model-View-Presenter (og lignende presentasjonsmønstre) at jeg så verdien, og nå bruker jeg mocking hele tiden. I min neste blogpost vil jeg gå mye mer i dybden på dette, og snakke om hvilke muligheter man har tilgjengelig for avhengighetsisolering i .NET.

Konklusjon

Utenfra-og-inn er en måte å kode på hvor vi driver frem designet av systemet gjennom tester. Vi går i små steg, og gjør oss ferdige med det vi holder på med før vi går videre. Ting vi ser vi trenger underveis mocker vi. Gjennom denne fremgangsmåten skiller vi bedre hva vi trenger fra hvordan det skal løses – vi fokuserer på det logiske designet og adferd, og det fysiske designet (implementasjonen om du vil) er underordnet og kommer etterhvert. Vi drives primært av forretningsbehov, og vokser systemet vårt organisk med de tingene vi oppdager at vi trenger.

Dette er uten tvil en herlig måte å jobbe på, og alle som ikke kjenner seg igjen i denne fremgangsmåten bør forsøke den.

Knagger: , , , ,

Ultra-tiny given-when-then DSL-snippet

Jeg har testet diverse TDD/BDD-rammeverk de siste månedene (bl.a. machien specification og tinyBDD), og eksperimentert endel med hvordan jeg kan skrive tester/spesifikasjoner som dokumenterer koden best mulig. Dette har resultert i at jeg ikke bruker noe spesielt rammeverk i det hele tatt, men bare bruker deskriptive navn og et generelt given-when-then formular.

Mine tester har en stund nå sett ut omtrent som dette:

        [Test]

        public void Stupid_car_start_1()

        {

            Given_transmission_is(manual);

            Given_transmission_is_in(first_gear);

            Given_clutch_pedal_is(not_engaged);

            When_key_is_turned();

            Then_car_should_jump_forward();

            Then_engine_should_be_dead();

        }

Det virker litt teit å si given tre ganger på rad. Alternativt kunne given nummer to og tre i stedet begynt med “And_”, og det samme kunne jeg gjort med Then_engine_is_dead(). Det er derimot et problemer med den måten å gjøre det på; ofte vil den metoden som er given nummer 3 i én test plutselig bli den eneste given i en annen test, og da kan den ikke hete noe med “And”.

Et annet problem er at det som er en “When” i én test kan være kjekk å bruke som en “Given” i neste test. For å løse dette problemet legger jeg til et lite sett med properties i test-klassen:

        #region Really tiny BDD DSL / Syntactic sugar

        protected CarStartTests Given { get { return this; } }

        protected CarStartTests When { get { return this; } }

        protected CarStartTests Then { get { return this; } }

        protected CarStartTests and { get { return this; } }

        #endregion

Her ser du hvordan den forrige testen blir seende ut ved å bruke de nye propertiene:

        [Test]

        public void Stupid_car_start_2()

        {

            Given.transmission_is(manual);

            and.transmission_is_in(first_gear);

            and.clutch_pedal_is(not_engaged);

            When.key_is_turned();

            Then.car_should_jump_forward();

            and.engine_should_be_dead();

        }

Men nå står jeg også mye mer fritt til å trikse å mikse med metodene og variablene i test-klassen, samtidig som jeg beholder et flytende og naturlig språk. Samme testen kunne f.eks. lett se ut som dette:

        [Test]

        public void Stupid_car_start_3()

        {

            Given.vehicle.Transmission = manual;

            and.vehicle.Transmission.SetGear(first);

            and.vehicle.Clutch.ChangeState(not_engaged);

            When.key.Turn();

            Then.car_jump_counter.should_be(1);

            and.vehicle.Engine.State.should_be(dead);

        }

I realiteten blir det som regel en blanding av de to fremgangsmåtene: Givens skjuler ofte mye, som f.eks. oppsett av mocks/fakes, og beholdes som metoder. Thens på den andre siden er som oftest minst like lesbare om de er tester direkte på en eller annen variabel brukt for å “sanse” korrektheten av testen/spesifikasjonen. Kombinert med et sett med fluent asserts (som du finner i coreTDD) blir en sjekk av en variabel like lesbar som et metodenavn.

Nedenfor er en Visual Studio snippet-fil jeg har definert for å enkelt kunne inserte de fire BDD-propertiene i nye testklasser. Hvis du lagrer XML’en med navnet bdd.snippet under “My Documents\Visual Studio 2008\Code Snippets\Visual C#\My Code Snippets”, så vil du kunne bruke den ved for eksempel å skrive “bdd” i koden din (uten hermetegnene altså..) og trykke TAB to ganger.

    1 <?xml version="1.0" encoding="utf-8" ?>

    2 <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">

    3     <CodeSnippet Format="1.0.0">

    4         <Header>

    5             <Title>Given-When-Then Syntax Sugar</Title>

    6             <Shortcut>bdd</Shortcut>

    7             <Author>http://blog.kjempekjekt.com</Author>

    8             <Description>Provides a really tiny dsl for doing bdd.</Description>

    9         </Header>

   10         <Snippet>

   11             <Declarations>

   12                 <Object Editable="false">

   13                     <ID>ClassName</ID>

   14                     <Function>ClassName()</Function>

   15                 </Object>

   16             </Declarations>

   17             <Code Language="CSharp">

   18                 <![CDATA[

   19                   #region Really tiny BDD DSL / Syntactic sugar

   20                   protected $ClassName$ Given { get { return this; } }

   21                   protected $ClassName$ When { get { return this; } }

   22                   protected $ClassName$ Then { get { return this; } }

   23                   protected $ClassName$ and { get { return this; } }

   24                   #endregion

   25                 ]]>

   26             </Code>

   27         </Snippet>

   28     </CodeSnippet>

   29 </CodeSnippets>

Hvis du finner noe mindre BDD-rammeverk enn dette så må du gi meg et vink ;)

Systemer som blogger

ayende På NDC 2009 hørte jeg bl.a. på Oren Eini (a.k.a. Ayende Rahien) snakke om Production Quality Software. Første del av sesjonen var ikke så veldig bra, men når han begynte å snakke om hvordan vi bør bygge inn driftsovervåkning i systemene våre ble jeg veldig inspirert.

Oren påpekte at drift er en viktig stakeholder i det meste vi lager, og at vi bør designe for dem. Vi bør synliggjøre systemets status og parametre på en god måte. Han foreslo at alle systemer (av en viss størrelse) bør ha en separat Operations Database som skal gi oss bedre innblikk i hva systemet til enhver tid driver med.

Ops-databasen skal inneholde observasjoner og forventninger. Observasjoner er det enkletse, og noe mange av oss gjør i dag. Å observere vil si å logge ting som skjer, som for eksempel “At date X I started some process Y with parameter Z”. Denne informasjonen kan være nyttig i mange tilfeller, ikke minst ved feilsøking (som ellers kan være utfordrende i et produksjonssystem).

Forventninger er anderledes – det kan være ting som at “90% av alle transaksjoner skal fullføres innen 5 sekunder” eller “jobb X skal kjøre hver uke”. Ops-databasen kan kontrollere disse forventningene, og trigge en eller annen form for alarm om en eller flere ikke er innfridd.

Hva hadde dette å gjøre med blogging sa du?

Det Oren spesielt foreslo var at systemet kan skrive til en blog. Alle systemer burde ha sin egen blog, og stakeholderne burde abonnere på bloggen. Bloggen til et system bør se ut og fungere som en blog skrevet av og for mennesker – titler og innhold i blogpostene må bestå av normalt språk, og det må ikke postes for hyppig (bedre å summere opp hendelser i én post om det skjer mye på en dag). Dette vil gi dem som leser bloggen et helt annet forhold til det bakenforliggende systemet.

Jeg syntes dette hørtes veldig spennende ut. Her er en mockup av hvordan jeg ser for meg en slik blog skal kommunisere:

Sample blog

De fleste systemer i drift har en SLA – en Service Level Agreement – som sier noe om hvordan systemet skal yte. Men vi, spesielt vi utviklere, har et alt for dårlig forhold til SLA’en. Hvordan vet vi at den er opprettholdt? Oren foreslår at Ops-systemet skal automatisere SLA’en. Systemet kjøre en automatisert audit, f.eks. hvert 30 minutt, som foretar spesifike operasjoner som innebærer database aksess, filaksess, eksekvering av prosesser, osv. Miljøet systemet kjører i kan verifiseres, tilgjengelig diskplass, responstid etc. kan måles.

Alle ulike komponenter i systemet bør ha sine egne SLA’er, og de må være laget sånn at det går an å verifisere dem. Disse vil fungere som automatiserte akseptansetester mens systemet er i drift. Observasjoner, forventninger og miljø-validering vil til sammen gi et komplett bilde av systemet i drift, man vil enklere kunne identifisere feil som inntreffer, og man vil kunne fange opp problemer før det blir så mange av dem at det får hele systemet til å “knele”.

En detalj, men likefullt et viktig poeng, er at administrasjonsgrensesnittet, hvor man kan aksessere Ops-databasen, må være separat fra produksjonsgrensesnittet. Hvis produksjonssystemet går ned må man sørge for at man fortsatt har tilgang til Operasions.

For å oppsummere: Vi må designe systemene våre med tanke på drift. Vi har hørt det før, men gjennomfører det sjelden på en god nok måte. Vi må gi driftsfolkene det de trenger, men ikke gi dem for mye, for da mister de oversikten og fokuset på hva som er viktig. Å la systemet skrive en operations blog er et eksempel på et bra grensesnitt. Den viser kun nøkkeldata / hendelser. Og la Ops-systemet foreta automatiske system-verifiseringer gjevnlig for å fange opp problemsituasjoner tidlig, og for å bevise at vår fantastiske konstruksjon overholder SLA’en.

Du kan se Oren’s Production Quality Software sesjon ved å følge denne linken.

Knagger: ,

Enhetstesting av konsoll-applikasjoner

I artikkelen min on Bellware’s NDC workshop inkluderte jeg en enhetstest jeg hadde skrevet for å teste output til System.Console. Her følger jeg opp med å forklare hvordan jeg gjorde det.

I Snookiepoof, hvor testen var hentet fra, ønsker jeg å ha så høy test coverage som mulig. Jeg utviklet nesten alt vha TDD/BDD, men brukegrensesnittet – et tynt lag som presenterte ting til konsollet – hadde jeg ingen tester for. Etterhvert begynte dette å irritere meg, og jeg måtte finne en måte å få dette under test på.

Her er et eksempel på en test jeg ønsket å kjøre:

    1 [Test]

    2 public void Should_output_new_value()

    3 {

    4     Given_a_ConsoleCreditChangedView();

    5     When_credit_changes_to(42);

    6     Output.should_contain(“42″);

    7 }

Jeg har et ConsoleCreditChangedView, en konkret implementasjon av et abstrakte CreditChangedView, som skriver til konsollet. Når viewet blir bedt om å vise en CreditChange med en bestemt verdi ønsker jeg å sjekke at den bestemte verdien blir skrevet ut til konsollet (linje 6).

Her er resten av detaljene fra testen, men uten å avsløre hvordan jeg får tak i Output (litt tålmodighet):

    9     Action Given_a_ConsoleCreditChangedView = () =>

   10         consoleCreditChangedView = new ConsoleCreditChangedView();       

   11 

   12     Action<int> When_credit_changes_to = (creditValue) =>

   13         consoleCreditChangedView.Show(new CreditChangedEventArgs(creditValue));

   14 

   15     static ConsoleCreditChangedView consoleCreditChangedView;

   16 }

Et naturlig valg hadde vært å abstrahere bort System.Console, og i stedet skrive til et interface, som jeg så kunne mocke bort i enhetstestene. Men jeg hadde brukte Console.WriteLine mange steder, og det ante meg at det fantes en annen løsning.

En rask titt på msdn-dokumentasjonen viste meg at jeg kunne erstatte konsollets default input og output. Det var akkurat det jeg trengte. Under normal kjøring kunne jeg la konsollet være som det er, mens jeg i enhetstestene kunne bytte ut input og output med noe jeg kunne aksessere og manipulere i testkoden.

Etter å ha prøvet og feilet litt kom jeg opp med løsningen nedenfor; én klasse for å “lure ut” output, én tilsvarende klasse for input, og en test fixture som kombinerte de to.

ConsoleTestingFixture

La oss ta en titt på ConsoleOutputFaker først:

    1 internal class ConsoleOutputFaker

    2 {

    3     private StringWriter _stringWriter;

    4 

    5     internal void SwapConsoleOutput()

    6     {

    7         _stringWriter = new StringWriter();

    8         System.Console.SetOut(_stringWriter);

    9     }

   10 

   11     internal string GetOutput()

   12     {

   13         return _stringWriter.ToString();           

   14     }

   15 

   16     internal void SwapBack()

   17     {

   18         RevertBackToOriginalOutput();

   19         DisposeResources();

   20     }

   21 

   22     private void RevertBackToOriginalOutput()

   23     {

   24         var standardOut = new StreamWriter(System.Console.OpenStandardOutput());

   25         standardOut.AutoFlush = true;

   26         System.Console.SetOut(standardOut);

   27     }

   28 

   29     private void DisposeResources()

   30     {

   31         if (_stringWriter != null)

   32         {

   33             _stringWriter.Close();

   34             _stringWriter = null;

   35         }           

   36     }

   37 }

I linje 7 og 8 kan du se hvordan jeg erstatter konsollets normale output med en StringWriter. Når programkoden min så skriver til konsollet så skrives det egentlig til denne StringWriter’en, og jeg kan hente ut og verifisere inneholdet (linje 13).

Resten av klasser er “cleanup” – linje 24 til 26 viser hvordan output settes tilbake til normalen igjen.

Som lovet lagde jeg også en input faker:

    1 internal class ConsoleInputFaker

    2 {

    3     private StringReader _inputReader;

    4 

    5     internal void SendInput(string text)

    6     {

    7         _inputReader = new StringReader(text);

    8         System.Console.SetIn(_inputReader);           

    9     }

   10 

   11     internal void SwapBack()

   12     {

   13         RevertBackToOriginalInput();

   14         DisposeResources();

   15     }

   16 

   17     private static void RevertBackToOriginalInput()

   18     {

   19         var originalInput = System.Console.OpenStandardInput();

   20         var originalReader = new StreamReader(originalInput);

   21         System.Console.SetIn(originalReader);

   22     }

   23 

   24     private void DisposeResources()

   25     {

   26         if (_inputReader != null)

   27         {

   28             _inputReader.Close();

   29             _inputReader = null;

   30         }

   31     }

   32 }

Som du ser er prinsippet det samme. Jeg setter konsollets input til en ny StringReader (linje 7 og 8). StringReader’en inneholder alerede teksten jeg ønsker å sende til konsollet, så nå kan jeg simulere brukerinteraksjon.

Som jeg sa kombinerer jeg så de to klassene i en test fixture:

    1 public class ConsoleTestingFixture

    2 {

    3     private static ConsoleOutputFaker fakeOut;

    4     private static ConsoleInputFaker fakeIn;

    5 

    6     static ConsoleTestingFixture()

    7     {

    8         fakeOut = new ConsoleOutputFaker();

    9         fakeIn = new ConsoleInputFaker();

   10     }

   11 

   12     [SetUp]

   13     public void TestSetUp()

   14     {

   15         fakeOut.SwapConsoleOutput();

   16     }

   17 

   18     protected static string Output

   19     {

   20         get

   21         {

   22             return fakeOut.GetOutput();

   23         }

   24     }

   25 

   26     protected static void SendInput(string text)

   27     {

   28         fakeIn.SendInput(text);

   29     }

   30 

   31     [TearDown]

   32     public void TestTearDown()

   33     {

   34         fakeOut.SwapBack();

   35         fakeIn.SwapBack();

   36     }

   37 }

Fixturen har en SetUp metode og en TearDown metode: Før hver enhetstest erstattes standard output med min fake, og etter hver enhetstest settes normal output og input tilbake til default.

Fixturen tilbyr så en property for å få tilgang til output (linje 18), og en metode for å sende input (linje 26). Test-klasser som arver fra denne fixturen kan bruke SendInput til å simulere at en bruker skriver noe til konsollet, og aksessere Output for å kontrollere hva programmet skriver til brukeren.

Du lurer kanskje på hvorfor jeg har brukt så mye static i test fixturen? Det kommer av måten jeg liker å skrive testklassene mine på for tiden – hvor jeg bruker Actions deklarert som instans-variabler – og da må det de skal ha tilgang til være static.

Her er et eksempel på en test som simulerer input. Denne klassen arver fra ConsoleTestingFixture, og får dermed tilgang til SendInput-metoden.

    1 [Test]

    2 public void Should_be_able_to_go_back_to_game_from_summary()

    3 {

    4     Given_a_known_ViewModel();

    5     When_the_summary_is_shown();

    6     And_the_user_types(back);

    7     The_user_selection_should_be(GameSummaryResult.ResumeCurrentGame);

    8 }

Og detaljene for And_the_user_types:

    1 Action<string> And_the_user_types = (input) =>

    2     SendInput(input);      

Så hvis du føler deg litt retro en dag og får lyst til å lage en konsoll app, så har du nå ingen unnskydning for å ikke skrive tester :)

Her er en annen blogpost om å erstatte Console.Out og Console.In som jeg brukte som referanse når jeg implementerte min fixture. Og her er en fyr som har gått for en løsning med attributter på test-metodene for å spesifisere input og forventet output.

Det kan også være lurt å ta en titt på hva test-rammeverket du bruker har for støtte av konsoll-testing “out of the box”. Skriver du mye konsoll-applikasjoner bør du kanskje også se på NConsoler, et open source biblotek for å bygge konsoll apps som også skal ha noe støtte for enhetstester (sies det).

Lurer du forresten på hvor should_contain metoden jeg kalte på Output i linje 6 i den aller første testen kommer fra? For å skrive lesbare tester er det ganske vanlig å erstatte de vanlige kallene til Assert med metodenavn som flyter bedre. Klikk her for å ta en titt på min FluentAsserts.cs som oversetter NUnits mange asserts til noe jeg er mere fornøyd med vha Extension methods. Koden er mer eller mindre rappet fra open source prosjektet coretdd, som også inneholder tilsvarene metoder for xUnit, MbUnit og MsTest.

Knagger: , , ,

God test, bedre kode

Av alle de spennende tingene som skulle skje på Norwegian Developer Conference 2009, var det en ting jeg gledet meg mere til enn noe annet. Jeg skulle nemlig få oppleve en hel dags workshop med Scott Bellware.


http://www.flickr.com/photos/digidragon/ / CC BY-NC-ND 2.0

Bellware er en utvikler og Agile coach fra Austin, Texas. Han har fått MVP-tittelen fra Microsoft fem ganger, men er mest kjent for å være en sentral person i Alt.Net miljøet, og kritiserer Microsoft ganske åpenlyst. Han er en fargerik person, og kan karrakteriseres som en aldri så liten bråkebøtte. Følger du @bellware på twitter så merker du raskt hva jeg mener.

Jeg var så heldig å få “henge litt” med Scott utenom workshopen og foredragene også, og selv om han er Stor i Kjeften har han mye interessant å komme med, og jeg synes det er helt fantastisk at Jon Arild har invitert Scott til å komme å forelese for NNUG i Bergen senere i år.

Workshop’en

Workshopen handlet i hovedsak om å skrive automatiserte tester i form av Context Specifictions. Scott’s budskap var at vi må gjemme all støyen fra testene/spesifikasjonene våre – testene er dokumentasjon, og skal være enkle å lese. Han sier at man oppnår høy produktivitet i utvikling om man har et godt design, og et godt design kommer av god TDD.

Med dette som bakteppe kom Bellware inn på mange ulike områder, og her er noen høydepunkter fra notatene mine:

TDD: De to første stegene i TDD – (1) lag en test som feiler og (2) få den til å passere på enklest mulig måte – er egentlig en test av selve testen. Ved å bevise at den først feiler og deretter passerer ved forventet “input til asserten” kan man stole på testen. Først etter at testen er grønn implementerer man funksjonaliteten slik den skal være. (Jeg har forsøkt å poengtere det samme flere ganger, men er ikke alltid like flink til å følge det selv.)

TDD: Gjør TDD i så små steg som mulig. “Jeg trodde at de små stegene Kent Beck presenterer i TDD boken sin var som de var for å illustrere prinsippene,” sa Scott. “Det tok meg to år å skjønne at det faktisk er sånn de beste utviklerne praktiserer TDD.”

TDD: Jobb bare med én test om gangen. Gjør du en endring som fører til at to tester må endres så gjør du sansynlig vis for mye på en gang.

TDD: Ikke bryt testene dine. Skal du gjøre en refakturering som krever en endring av testen er det bedre å legge tilrette for å endre testen uten å fjerne den eksisterende implementasjonen. Når alt er på plass kan du endre testen, uten at den behøvde å bli rød på noe som helst tidspunkt. Til slutt kan du fjerne den delen av implementasjonen som støttet den gamle versjonen av testen.

TDD: Husk å alltid skrive hva du ønsker skal eksistere før du implementerer det. Ikke bar når du skriver tester. “Jeg trenger en metode” oppdages ved å kalle metoden (som ikke finnes enda). Først deretter implementeres den.

DDD og IoC: Unngå dependency injection inn i metoder

DDD og IoC: Unngå dependency injection inn i Enteties

DDD: Enteties bør ikke tilby aggregert data (rapportdata) – som f.eks. TotalOfPreviousOrders

DDD: customer.Orders er no no! repository.GetOrders(customer) er bra!

Verktøy: Check in til kildekode-systemet hele tiden! Hver gang du har gjort en endring som fungerer: Get latest, run the tests (nant/rake), and commit changes.

Verktøy: Autohotkey er et interresant verktøy. Scott gav oss et autohotkey script som lar deg bytte ut space med underscore og dermed gjør den enklere å skrive metode_og_klassenavn_som_dette.

Verktøy: Sett opp shortcut for å kjøre en eller flere tester med TestDriver.net og en annen shortcut for å kjøre tester om igjen. Dermed går det raskere å kjøre samme test eller testsett flere ganger mens man jobber i koden.

Scott brukte også endel tid på å snakke om det jeg kjenner som the onion architecture. Han beskrev dette som et grunnleggende Alt.Net pattern som brukes over alt for tiden. Det virker veldig logisk og i trå med alt jeg har lest av Uncle Bob i det siste, og jeg må sette meg grundig inn i dette og finne ut hvordan det vil endre hvordan jeg designer løsningene mine.

En av testene vi skrev..

Her er et eksempel på hva vi produserte i workshopen. Denne spesifikasjonen beviser at en ordre tar høyde for avslag når man ber om prisen. Scott brukte MSpec, Machine Specification, til å skrive testene.

    9 [Subject("Order")]

   10 public class when_discounting_an_order

   11     : OrderContext

   12 {

   13     Establish context = () =>

   14     {

   15         order = order_with_total_of(100m);

   16         order.DiscountPercentage = 10m;

   17         order_total = order.GetTotal();

   18     };

   19 

   20     It should_discount_the_order_by_10_percent = () =>

   21     {

   22         order_total.ShouldEqual(90m);

   23     };

   24 }

Spesifikasjonen er enkel å forstå, den lar seg lese nesten som vanlig engelsk. Når man har en ordre med en verdi av 100 dollar, og man gir 10% avslag, så skal prisen reduseres med 10%, hvilket vil si at totalen skal være 90 dollar.

Detaljene er gravd ned i OrderContext, som spesifikasjonen arver fra:

   26 public class OrderContext

   27 {

   28     public static Order order_with_total_of(decimal amount)

   29     {

   30         var newOrder = new Order(new Customer());

   31         var item = new Item(amount);

   32         newOrder.AddItem(item);

   33         return newOrder;

   34     }

   35 

   36     protected static decimal order_total;

   37     protected static Order order;

   38 }

Jeg har tidligere blogget om hvordan jeg har gått fra å skrive tradisjonelle enhetstester til å skrive GivenWhenThen senarioer med et rammeverk som heter TinyBDD. Bellware gav meg mange flere tips om hvordan slike spesifikasjoner bør konstrueres. Det ironiske er at jeg etter dette har droppet å bruke BDD-rammeverk til fordel for å skrive mine egne DSL’er. TinyBDD, MSpec og lignende skaper mer støy enn nødvendig, og ved å skrive ren og selvdokumenterende kode føler jeg at jeg kan oppnå vel så god effekt uten dem.

Her er et eksempel på hvordan testene jeg skriver for tiden ser ut:

    1 [TestFixture]

    2 public class ConsoleGameIntroViewTest : ConsoleTestingFixture

    3 {

    4     [Test]

    5     public void Should_display_introduction_text()

    6     {

    7         Given_a_game();

    8         With_introduction(This_is_an_introduction);

    9         When_displaying_view();

   10         Output.should_contain(This_is_an_introduction);

   11     }

   12 

   13     [Test]

   14     public void Should_display_title()

   15     {

   16         Given_a_game();

   17         With_title(A_Long_Time_Ago_in_a_Galaxy_Far_Far_Away);

   18         When_displaying_view();

   19         Output.should_contain(A_Long_Time_Ago_in_a_Galaxy_Far_Far_Away);

   20     }

   21 

   22     Action Given_a_game = () => gameLevel = new GameLevel();

   23 

   24     Action<string> With_introduction = (introduction) =>

   25         gameLevel.IntroductionText = introduction;

   26 

   27     Action<string> With_title = (title) =>

   28         gameLevel.Title = title;

   29 

   30     Action When_displaying_view = () =>

   31     {

   32         introView = new ConsoleGameIntroView();

   33         introView.Show(gameLevel);

   34     };

   35 

   36     static GameLevel gameLevel;

   37     static ConsoleGameIntroView introView;

   38     const string This_is_an_introduction = “This is an introduction”;

   39     const string A_Long_Time_Ago_in_a_Galaxy_Far_Far_Away = “A Long Time Ago in a Galaxy Far Far Away”;

   40 }

Dette er en testklasse for et View i et konsoll-basert spill som bruker Model-View-Presenter. Det viktigste kommer først – selve testene. Og de kan leses av hvem som helst, og lar deg fokusere på adferden – implementasjonsdetaljene er støy, så de kommer lengre nede.

Du synes kanskje dette er galskap? Jeg føler denne måten å spesifisere adferd på hjelper meg å skrive bedre kode, og de dokumenterer ganske bra hva systemet skal gjøre. Forvent mer om mine enhetstester i kommende blogposter..

Mer Bellware?

Er du interessert i å høre mer om hva Bellware har å si om TDD/BDD anbefaler jeg at du lytter til Hanselminutes #146: Test Driven Development is Design – The Last Word on TDD.

Og er du mer interessert i hans bråkete side kan du lytte til Alt.Net podcast #17: The State of Alt.Net, hvor han rakker ned på miljøet han har vært med å bygge opp. I så fall bør du også laste ned show nummer 18: Talking with Jeremy Miller about Alt.Net, som forsøker å roe ned situasjonen igjen.

Knagger: , , , , , , ,


Torbjørn: La oss anta to ulike definisjoner av Template Method pattern - de to ytterpunkte...

Lars-Petter: Hei igjen. Siden du inviterer til å ta diskusjonen i bloggen, og har tatt deg t...

Torbjørn: Lars-Petter: Det er én måte å se det på. Det er absolutt fortsatt Template M...

Lars-Petter: Hei. Har du ikke i prinsippet her gått over fra Template Method (arv) til Strat...

Christian Abildsø: I alle fall i C#, så føles dette som kode som blir mer fleksibel men vanskelig...

Torbjørn: Hei Henrik, og takk for tilbudet. Ble oppmerksom på Rasberry Pi for under en uk...

Henrik Sandaker Palm: Ang. større hobby prosjekt. Du er som er en slik rakker på programmering har j...

Øivind Nilsen: Slutt å bruke mobilnummeret mitt som eksempel !...

Bjørn Einar Bjartnes: Jeg har også latt meg fascinere av Clojure, uten at jeg har kommet så veldig l...

Bjørn Einar Bjartnes: Sweet :) Jeg tror egentlig jeg liker det som det er, med musikk. Litt av utford...

 Hold deg oppdatert

Søk i bloggen

Ferske innlegg

  • Template Method del 4: Multippel arv
  • Template Method Intermesso
  • Template Method del 3: Bare funksjoner
  • Template Method del 2: På vei mot funksjonell programmering
  • Kategorier

  • .net ninja (37)
  • Bøker (17)
  • Diverse prosjekter (35)
  • DSL (10)
  • Erlang (10)
  • F# (5)
  • Hardware (1)
  • Jobb (77)
  • Julekalender (51)
  • kjempekjekt.com (23)
  • LISP/Clojure (33)
  • NNUG / community (60)
  • O/RM & databaser (10)
  • Off topic (116)
  • OO-design/clean code (30)
  • Podcasts (14)
  • Polyglot (77)
  • Ruby (27)
  • Silverlight / RIA (3)
  • Software/verktøy (20)
  • Softwareutvikling (20)
  • Testing / TDD (30)
  • the contiki strip (13)
  • User experience (3)
  • WCF (3)
  • Webutvikling (32)
  • WPF (9)
  • WTF (12)
  • Last ned Wallpaper

    Programmeringsbloggens tøffe skrivebordsbakgrunn med snippets fra ulike språk laster du ned her!

    Abonner via epost

    Om du vil kan du få alle nye blogposter tilsendt til din epost. Abonner nå, det er kjempeenkelt!

    Meta