Thursday, August 27th, 2009
Skriv en kommentar

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: , , , ,

Kategorier: Software/verktøy, Testing / TDD.
RSS feed for kommentarene. Tilbaketråkk.

4 kommentarer til “Avhengighetsisolering (a.k.a. Mocking) i .NET”

  1. Glenn F. Henriksen Says:

    Veldig bra forklaring om mocking (eller isolering som Osherove vil vi skal kalle det). Ser stadig frem til postene dine

  2. Johannes Brodwall Says:

    God oppsummering av forskjellige typer test doubles.

    For Java-programmerere det ute: Innen Java har jeg i det siste brukt rammeverket Mockito. Dette har en veldig konsis og typesterk syntaks og oppfordrer til å ikke spesifisere mer oppførsel enn det som er nødvendig, slik at testene blir mer robuste.

    Mockito minner en del om Moq, men er litt mer konsist og magisk når du spesifiserer et metodekall. Så ditt Login-eksempel blir:

    LoginView view = Mockito.mock(LoginView);
    Mockito.when(view.getPassword()).thenReturn(“passw0rd”);

    UserRepository repository = Mockito.mock(UserRepository);
    Mockito.when(repository.findByName(Mockito.any())).thenReturn(new User(“BED128365216C019988915ED3ADD75FB”));

    Med static imports blir det enda mer sveet.

  3. Morten Haug Says:

    Fin post igjen Torbjørn, herlig forfriskende lesning på norsk!

    Jeg syns vi skal være konsekvente og bruke Test Doubles som et samlebegrep (som Johannes bruker over) for Test Stubs, Test Spies, Fake Objects og Mock Objects. Gerard Meszaros har laget en fantastisk god samling i sin bok XUnit Test Patterns (se også http://xunitpatterns.com/). Jeg ser ikke begrepsforvirring som noe unnskyldning for å ikke å bruke fornuftige og beskrivende begreper, som atpåtil er beskrevet detaljert i en koloss av en bok (størrelse og kvalitet)! Hvis vi som kan litt om dette ikke kan bruke et felles begrepsapparat er det ikke rart Olsen i gata ikke helt får grepet om det hele. Stubs kontrollerer indirekte inndata, spies kontrollerer indirekte utdata og fakes er reelle, men alternative, kjappere og/eller sideeffektfrie avhengighetskomponenter. Hva mocks legger på er selvsjekking. Jeg vet ikke hvorvidt dette sammenfaller med hva bl.a. Roy Osherove skal ha sagt om saken (som er nevnt i kommentarer i din forrige post). I en intern presentasjon hos oss brukte jeg henholdsvis de norske begrepene teststuntmenn, teststubber, testspioner og bedragere for de 4 første. Du er god med ord, hva ville du foreslått?

    Det er hvertfall 2 store farer jeg har erfart med mock objects (i Meszaros’ betydning) og da spesielt når man benytter 3. parts rammeverk for jobben. Den første feilen er å overspesifisere forventningene og låse implementasjonen. Det er en sammenheng mellom å ikke helt forstå rammeverket og feil nummer 2. Feil nummer 2 er knyttet til mange elementer, men manifester seg i at man mocker hele objekter og ikke roller (se “Mock Roles, Not Objects” http://www.jmock.org/oopsla2004.pdf). Dette gjør det ekstremt enkelt å bryte med ISP og SRP.

    Etter at vi virkelig tok innover oss ISP (ev. Role Interfaces (Fowler))(for ikke å snakke om skikkelig objektorientering!), så ble også behovet for Mock Objects og tilhørende rammeverk mer overflødig. Så på et vis dekket de over et problem vi hadde som ikke kom til overflaten fordi vi brukte et slikt rammeverk. Vi lager oftere nå konfigurerbare manuelle stubs, fakes og spies, da implementasjonen av disse nette interfacene ikke er store jobben, og vi får veldig mye bedre kode. Vi har sakte men sikkert begynt å bruke mer Mock Objects igjen, men de er i mindretall.

    Har sett over kommentarer i din forrige post om temaet nå og ser at dette godt kunne vært postet der, men nå ble det her i stedet!

    mvh
    Morten (haugern@twitter)

  4. Torbjørn Says:

    Hei, Haugern, og takk for en bra kommentar. Du spør meg hvilke ord jeg ville brukt, men jeg må innrømme at jeg bare får vondt i hodet av alle disse begrepene, og tør ikke tenke på all forvirringen jeg vil påføre åtte-til-fire-utviklere (i mangel av et bedre uttrykk) om jeg skulle forsøkt å gi dem en oversikt over dette. Simplicity skal være et mantra for smidige utviklere, men dette er langt fra enkelt.

    Som jeg har sagt tidligere, jeg ser ikke hensikten med å skille konseptene så sterkt i praksis. “Teststuntmennene” jeg bruker er som regel både stubs og mocks på en gang, og hvis jeg forstår hva du mener med “spies” (første gang jeg hører om dem) så er de gjerne det også. Poenget er at de er stand-ins for avhengigheter som lar meg kjøre koden i en klasse, og verifisere at den gjør som forventet. Som utvikler trenger jeg ikke mer enn det.., resten er kun av akademisk/filosofisk interesse – interessant, men ikke praktisk.

    Test Doubles kan være et greit samlebegrep, om vi ser bort fra at enkelte ikke har lyst å snakke om tester i det hele tatt (the BDD / Code-by-example crowd). Dessuten kan fakes og mocks være nyttige teknikker andre steder enn i tester, som ved prototyping, eller før man integrerer løsninger mot eksterne systemer. For meg er “Mocking” fortsatt det ordet utviklere flest har hørt noe om, og vi kaller verktøyene for mockingrammeverk, så den pragmatiske løsningen er å bruke det.

    Når det kommer til fallgruve nummer 1 så er jeg enig – det er absolutt en fare for at man kan overspesifisere forventningene. Jeg er derimot ikke helt sikker på at denne faren er større når man bedriver mockist TDD enn om man driver med klassisk (state) TDD. [Se min kommentar på utenfra-og-inn programmering for mer om de begrepene.] Når man mocker (les: bruker mange test doubles) har man en sterkere tendens til å spesifisere forventninger knyttet til adferd, kontra det å knytte forventninger til tilstandsendringer. Jeg skal ikke påstå at det er enklere å trå feil med tilstandsforventninger, men jeg TROR det er det. Trenger å jobbe mer med problemstillingen.

    Fallgruve nummer 2 fikk meg til å tenke litt, men jeg tror ikke det er et problem som kommer som en følge av å bruke mocking. Hvis designet følger ISP og SRP så vil mockingen følge ISP og SRP. Og jeg er av en klar oppfatning at utenfra-og-inn programmering vil hjelpe deg til å følge disse prinsippene. Mocking gjør det klart enklere for meg å følge SRP, det er helt sikkert. Og ikke bare SRP i designet, men også SRP under utvikling – ved at jeg kan konsentrere meg om ett ansvar om gangen, og mocke resten. Når jeg koder utenfra-og-inn er det nettopp “roller” jeg oppdager, ikke objekter. Objektene kommer i neste rekke, når jegbevegere meg nedover i avhengighets-treet.

    Jeg forstår ikke hvorfor du synes konfigurerbare, manuelle stubs, fakes og spies gir veldig mye bedre kode. Kanskje er det en smaksak, men et mockingrammeverk gir mindre kode å vedlikeholde, kommuniserer godt om det brukes riktig, og koblingen mellom testen og test-double blir tettere.

    Til slutt et lite forbehold: Jeg har ikke mocket lenge nok til å se hvilken effekt det har på testene mine over lang tid – når designet endrer seg drastisk. Det kan godt tenkes jeg er skyldig i å låse implementasjonen mer enn jeg burde. I så fall gleder jeg meg til den dagen jeg oppdager det, for da har jeg lært noe. Mocking har brakt min TDD-teknikk til et nytt nivå, men det er LANGT igjen til fullstendig mestring – om det i hele tatt er mulig å oppnå.

    Igjen, takk for at du deler dine tanker og meninger!

Skriv en kommentar

Tillatte tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Siste kommentarer

Torbjørn
PS: Takk til Børge Hansen, som delte SCARF-modellen med meg!...
Børge Hansen
Denne likte jeg veldig godt. Du skriver godt og har gode betraktninger  Keep it up – flere trenger å tørre å lære mer om ledelse – du l...
Tormod
Er egentlig ikke overrasket. F# sin fortè er programmererens produktivitet/kvalitet og anledning til parallell kjøring. Men kjøremotoren har ...
Stian
Ville også prøvd med et større problem (x100 eller x1000 f.eks). Når man snakker så små brøkdeler av et sekund som her så kan tiden for en ell...
Torbjørn
Har ikke sjekket - tar en titt i morgen hvis tid :)...
Einar W. Høst
Mhp tco: hva sier ILSpy?...
Torbjørn
Har ikke sett noe på PSeq før, men kjenner til den typen funksjoner fra blant annet Clojure. Og problemet med slike funksjoner i sammenhenger som de...
Håvard
Veldig bra sammenligning! Har du sett på ytelsen av PSeq.* fra powerpakken? Tipper den vil gi performancehit på små mengder, men kan kanskje resul...
Torbjørn
Jeg kom på en demonstrasjon-variant til jeg burde inkludere, nemlig bruk av list comprehension (en type computation expression (også kalt monads)). ...
Einar W. Høst
Interessant, det blir en trade-off mellom eleganse og fart på en måte. Den funksjonelle løsningen med vanlig filter er ren og pen, mens den imperat...
Creative Commons-lisens
Innholdet på denne bloggen er tilgjengelig under Creative Commons Navngivelse-Ikkekommersiell-DelPåSammeVilkår 3.0 Norge lisens.

Programmeringsbloggen
Kjempekjekt.com

© 2006-2013 Torbjørn Marø

Jeg har vært en profesjonell programmerer siden 1999, og dette er min blogg. Målet med bloggen er å stimulere meg selv og alle andre til kontinuerlig eksperimentering og læring.

Jeg forsøker å være allsidig, og programmerer blant annet i C#, Ruby, Erlang og Clojure.

Jeg praktiserer TDD og andre smidige utviklingspraksiser. Jeg er opptatt av kvalitet og ren kode.

Dette og ganske mye mer kan du lese om på denne bloggen!