Testing / TDD

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

Cobra

Programmeringsspråket du skal få møte i dag har mye til felles med Boo, språket jeg fortalte om i går. Cobra er nemlig også laget for .NET og Mono, og syntaksen er Python-inspirert (men Ruby-utviklere vil også kunne føle seg som hjemme). Det har både statisk og dynamisk binding, og er generelt sett en smeltedigel som forsøker å kombinere det beste fra mange andre språk.

cobra

Hvorfor bruke tid på Cobra?

Cobra er utviklet med fokus på å øke kvaliteten i koden din. For det første er enhetstester førsterangs borgere i Cobra. De lever som en naturlig del av metode-definisjonene, og kjøres som en del av kompileringen.

Men i tillegg til normale tester støtter språket også kodekontrakter, inspirert av Eiffel. Dette innebærer at du kan deklarere invarianter, pre- og post-conditions som objekter og metoder må tilfredstille. Kontraktene blir en del av grensesnittet til klassene, og brudd på en kontrakt under kjøring resulterer i exceptions.

Ønsker man optimal ytelse kan både tester og kontrakter enkelt elimineres når man kompilerer for produksjon.

Andre interessante egenskaper er compile-time nil-tracking. Skal noe kunne være en nil (eller null om du vil) i Cobra så må det deklareres eksplisitt. Er du lei av å bli overrasket av NullReferenceException når du kjører programmet ditt? Da kan dette være noe som hjelper deg.

Cobra tilbyr også utvidet informasjon når et problem oppstår. Man kan f.eks. velge å få generert en HTML-formatert post-mortem rapport når programmet kræsjer.

Et eksempel

Jeg tipper du ikke lar deg overraske av at jeg skal bruke Cobra til å finne summen av alle positive heltall under 1000 som er multipler av 3 eller 5. Klassen nedenfor implementerer et komplett program som skriver ut løsningen.

Euler1-klassen inneholder to metoder: main, som kjøres når programmet eksekveres, og validNumber, som inneholder både tester og en liten precondition.

10 class Euler1
11     shared
12         def main
13             """
14             Finds the sum of all numbers from 1 
15             below 1000 that are multiples of 3 or 5.
16             """
17             sum = 0
18             for n in 1:1000
19                 if .validNumber(n)
20                     sum += n
21             print sum
22 
23         def validNumber(n) as bool
24             require
25                 n > 0
26             test
27                 assert .validNumber(6)
28                 assert .validNumber(10)
29                 assert not .validNumber(1)
30                 assert not .validNumber(8)
31             body
32                 return n % 3 == 0 or n % 5 == 0

Noen bekymringer

Cobra har vært under utvikling i mange år nå, men virker fortsatt litt uferdig. Det skal blant annet støtte mixins, men dette virker ikke som det skal. Støtten for anonyme metoder og lamda er heller ikke så bra enda. På den andre siden har det blitt laget en Cobra add-in for Visual Studio 2010, men denne har jeg ikke forsøkt.

Jeg er litt usikker på hvor aktiv utviklingen egentlig er. Jeg ser det stadig gjøres små bugfikser, men gjeldende release er over ett år gammel. Det er også endel som ikke er oppdatert på websidene, men diskusjonsforumet er i alle fall aktivt.

Tenker man at Cobra kan være noe å satse på bør man derfor vurdere disse tingene nærmere.

Hvordan komme i gang

Cobra finner du på cobra-language.com. Der kan du laste ned språket, og så er det rimelig enkelt å komme igang. Dokumentasjonen er brukbar, men som sagt inneholder den både hull og enkelte ting som har gått ut på dato.

Det er likevel interessant å teste ut et språk hvor Design by Contract-teknikken har fått en så sentral plass. Microsoft er jo i ferd med å levere støtte for dette i sine egne språk på .NET-plattformen, og det kan være lurt å lære seg dette som et suplement til testdrevet utvikling basert på enhetstester.

"Rename method" kan være farlig

Denne historien fra virkeligheten forteller om en tilsynelatende uskyldig og ufarlig refakturering som førte til at en katastrofal feil ble deployet til produksjon. Jeg har anonymisert og forenklet koden for å beskytte den skyldige (som var undertegnede selv) og for å gjøre det enkelt å forstå hva problemet er.

Det hele begynte med en nokså sentral klasse som vi skal kalle SomeBaseClass. Den hadde blant annet en metode vi for anledningen kaller DoSomething.

1     class SomeBaseClass
2     {
3         public void DoSomething()
4         {
5         }
6     }

Etterhvert som systemet vokste ble det innført mange klasser som arvet fra SomeBaseClass. En av disse var SomeDescendant.

1     class SomeDescendant : SomeBaseClass
2     {
3         public void DoIt()
4         {
5             DoSomething();
6         }
7     }

Alt fungerte som det skulle i lange tider, og alle var glade. Men så en dag fant en utvikler ut at han ikke likte navnet på metoden DoSomething. Heldigvis hadde han CodeRush-plugin i Visual Studio, og var derfor ganske sikker på at han raskt og trykt kunne endre navnet på metoden med den automatisert refaktureringen rename.

Utvikleren bestemte seg for at DoIt var et mye bedre og mere beskrivende navn!

Uheldigvis fungerte refaktureringen som den skulle. Koden kompilerte, testene var grønne, og endringen ble med i neste release. Utvikleren husket ikke at han allerede hadde kalt en av metodene i SomeDescendant for det samme.

10     class SomeBaseClass
11     {
12         public void DoIt()
13         {
14         }
15     }
16 
17     class SomeDescendant : SomeBaseClass
18     {
19         public void DoIt()
20         {
21             DoIt();
22         }
23     }

Ser du problemet? DoIt i SomeDescendant er nå en evig rekursjon.

Blir den kalt vil den føre til et stack overflow exception som det ikke er mulig å fange. Eller hvis JIT-kompilatoren har klart å optimalisere rekursjonen (TCO), noe jeg faktisk tror var tilfellet her, så vil programmet bare stå og spinne inn i uendeligheten eller til serveren brenner opp.

Bug retrospective

Burde denne feilen ha blitt oppdaget før release? Var det tankeløst å gjennomføre en sånn refakturering? Burde C# tillate denne koden i det hele tatt?

Jeg har alltid sett på rename som en veldig trygg refakturering i et IDE som Visual Studio og et statisk typet språk som C#, og de fleste av oss gjør det sikkert hele tiden. Navngivning er veldig viktig, og føler man at et navn er feil så bør man endre det.

Endringen førte derimot til at kompilatoren genererte en warning. Jeg har forsøkt å bruke “treat warnings as errors” opsjonen, men i praksis er det så mange “false positives” i større prosjekter at det å vedlikeholde alle unntakene blir en urimelig oppgave. Jeg tar manuelle gjennomganger av warnings fra tid til annen, men det er ikke rart at ting faller under radaren.

Vi kunne ha innført en kodestandard fra starten hvor vi alltid kalte metoder i baseklassen eksplisitt – altså base.DoIt() – men det ville også vært vanskelig å overholde.

Den eneste feilen jeg er villig til å innrømme var å ha for dårlig test coverage på koden! Hadde SomeDescendant.DoIt() blitt kalt i en test, ville problemet ha blitt oppdaget. Grunnen til at dette ikke ble gjort i tilfellet her var at DoIt i utgangspunktet var logikkløs, og at funksjonaliteten var blitt testet andre steder. Det føltes veldig trygt å ikke teste. Og det er alltid i slike tilfeller man angrer bittert i etterkant!

Det jeg sitter igjen med er likevel spørsmålet om C# burde tillate dette i det hele tatt. Som warningen sier: ‘SomeDescendant.DoIt()’ hides inherited member ‘SomeBaseClass.DoIt()’. Use the new keyword if hiding was intended. Hvorfor kunne ikke dette vært en error i stedet? Finnes det reelle tilfeller hvor “hiding” uten “new” faktisk kan være nyttig? En error ville gjort at jeg hadde oppdaget problemet og fikset det uten problemer.

Bowling Kata

Jeg har laget en ny video til dere; denne gangen har jeg tatt opp mitt forsøk på å gjennomføre Bowling Kata i Clojure (bakgrunn om Bowling Game Kata på codingdojo.org og butUncleBob.com).

Jeg sier forsøk fordi jeg knoter litt underveis. Jeg valgte derimot å beholde videoen slik den var i stedet for å ta den opp igjen – å se hvordan jeg feiler, og hvordan jeg får hjelp av testene til å hente meg inn igjen, har også en viss verdi.

Så hvis du trenger litt TDD og/eller Clojure-inspirasjon, her har du en ærlig og munter code cast:

Praktisk SpecFlow på NNUG Bergen

vagif1_reasonably_smallDenne uken skal vi ha det mest interaktive møtet i NNUG Bergen’s historie (såvidt jeg vet/kan huske). På onsdag får vi nemlig besøk av Vagif Abilov, som skal lære .net-utviklerne alt de behøver å vite om såkalte “kjørbare spesifikasjoner”.

For omtrent et år siden skrev jeg blogposten en agurktest, hvor jeg presenterte Cucumber – et fantastisk rammeverk for å gjøre høynivå-TDD, eller BDD om du vil. Man kan beskrive ønsket funksjonalitet i et domenespesifikt språk som i praksis er vanlig engelsk, og så kjøre beskrivelsene og få verifisert om funksjonaliteten eksisterer og virker.

Og da nevnte jeg også SpecFlow, som er et prosjekt kraftig inspirert av Cucumber, laget for oss som er på .net-plattformen.

Vagif vil først introdusere oss for ideene bak Cucumber, Gherkin (det domenespesifike språket) og SpecFlow, og deretter slippes alle møtedeltagerne løs på en programmeringsoppgave hvor man skal bruke SpecFlow.

Du må derfor ta med deg din egen laptop om du kan, hvor du må ha installert Visual Studio 2008 eller 2010, siste versjon av SpecFlow (er selvsagt gratis), og ha fulladede batterier. Har du ikke med egen maskin vil du bli paret opp med en som har, så det er egentlig ikke noe problem.

Jeg håper det kommer mange, selv om det er like etter påske, og mange derfor ikke har fått det med seg enda.

Registrer deg for møtet her >>

Strømlinjeformede enhetstester

Når man skriver enhetstester kan det fort bli mye rot om man ikke tenker nøye gjennom hvordan de bør struktureres. Det vanlige er å lage testmetoder som inneholder en eller annen form for Arrange-Act-Assert eller Given-When-Then; man setter altså opp et miljø som skal testes, utfører en handling, og sjekker at det man forventer har skjedd. Desverre blir dette ofte for mye støy i én metode, og man trenger et bedre “rammeverk” for testene.

Ingen situasjoner er like, og strukturen på testene bør derfor heller ikke alltid følge samme mal. I denne blogposten forteller jeg om et mønster som ofte gir meg renere enhetstester..

Her er et eksempel hvor jeg utvikler et lite språk (DSL) for å svare på meldinger. Reply_with_message.sms er et skript skrevet i dette språket som automatisk skal svare med teksten fra den innkommende meldingen flettet inn.

Reply_with_message.sms:
 10 reply "You said: \"${message}\""

For å kontrollere at dette fungerer har jeg laget følgende enhetstest i C#:

 12 [TestScript("Reply_with_message.sms", Sender = "55596698", Text="This is a test")]
 13 public class Spec_reply : CompileAndEvaluateTestFixture
 14 {
 15     [Test]
 16     public void Should_reply_once_and_only_once()
 17     {
 18         Result.Replies.Count.ShouldEqual(1);
 19     }
 20     [Test]
 21     public void Should_reply_with_original_text()
 22     {
 23         Result.Replies.First().ShouldEqual("You said: \"This is a test\"");
 24     }
 25 }

Testklassen inneholder to tester: Den første kontrollerer at det er registrert én utgående melding, og den andre kontrollerer at teksten i meldingen er som forventet.

Testklassen er dekorert med en attributt som spesifiserer navnet på skriptfilen, og informasjon om meldingen den skal behandle. Det er baseklassen som testen arver fra som gjør selve jobben. CompileAndEvaluateTestFixture plukker ut skriptfilen, kompilerer den, og bruker resultatet til å prosessere meldingen jeg spesifiserte. Propertien Result inneholder resultatet.

Dette gir en ren og pen enhetstest, og baseklassen kan gjenbrukes i mange andre tester.

Et komplett eksempel med baseklasse og attributt

Nå har du sett hvor jeg vil hen. La meg ta et annet eksempel, og forklare litt grundigere.

For det første bør jeg nevne hvilke avhengigheter jeg bruker. Hver gang jeg starter et nytt prosjekt kopierer jeg alltid med meg tre ting: Testrammeverket NUnit, mockingrammeverket Moq, og coreTDD for flutende assert (f.eks. ShouldEqual()).

Og så til eksempelet: Her har jeg utviklet en Lexer (også kalt tokenizer eller scanner) som skal ta en kodesnutt og splitte det opp i tokens. Og her er en ren og pen test som eksekverer lexeren på strengen foo = "Some text":

 29 [TokenizeThis("foo = \"Some text\"")]
 30 public class AssignmentTest : BaseLexerTest
 31 {
 32     [Test]
 33     public void Test()
 34     {
 35         ExpectTokens(Tokens.ID, Tokens.ASSIGN, Tokens.STRING);
 36         ExpectTokens("foo", "=", "\"Some text\"");
 37     }
 38 }

Igjen bruker jeg en attributt for å spesifisere dataene for testen, og en baseklasse utfører selve handlingen. Testmetoden kan dermed konsentrere seg om å kontrollere output.

Her er den enkle implementasjonen av attributtet:

177 public class TokenizeThis : Attribute
178 {
179   public TokenizeThis(string input)
180   {
181     Input = input;
182   }
183   public string Input { get; private set; }
184 }

Og her følger baseklassen som gjør hele jobben. I setup-metoden som kjøres før hver test henter jeg først ut input-teksten fra attributtet, oppretter en ny Lexer, og utfører operasjonen. Linje 141-142 er bare debug-output. De to siste metodene er verktøy enhetstestene så kan benytte for å kontrollere resultatet.

126 [TestFixture]
127 public class BaseLexerTest
128 {
129   private Lexer lexer;
130   private TokenizeThis _attributes;
131 
132   public List<Token> tokens { get { return lexer.Tokens; } }
133 
134   [SetUp]
135   public void SetUp()
136   {
137     GetTextToTokenize();
138     lexer = new Lexer(Tokens.All);
139     lexer.Tokenize(_attributes.Input);
140 
141     Console.WriteLine("Tokens for {0}", _attributes.Input);
142     tokens.ForEach(tok => Console.WriteLine(tok));
143   }
144 
145   private void GetTextToTokenize()
146   {
147     try
148     {
149       _attributes = this.GetType()
150         .GetCustomAttributes(typeof(TokenizeThis), false)
151         .First() as TokenizeThis;
152     }
153     catch (Exception ex)
154     {
155       Assert.Fail("Test class {0} does not specify a TokenizeThis attribute!",
156           this.GetType().Name);
157     }
158   }
159 
160   protected void ExpectTokens(params int[] tokenTypes)
161   {
162     tokens.Count.ShouldEqual(tokenTypes.Length);
163 
164     for (int i = 0; i < tokenTypes.Length; i++)
165       tokens[i].Type.ShouldEqual(tokenTypes[i]);
166   }
167 
168   protected void ExpectTokens(params string[] tokenContent)
169   {
170     tokens.Count.ShouldEqual(tokenContent.Length);
171 
172     for (int i = 0; i < tokenContent.Length; i++)
173       tokens[i].Text.ShouldEqual(tokenContent[i]);
174   }
175 }

Jeg har altså laget en attributt som lar meg spesifisere Arrange-delen (eller Given) av testen, og en baseklasse som tar seg av å utføre Arrange og Act (eller Given og When), samt gjøre det enklere for testene å utføre Assert (eller Then).

Denne teknikken gir meg mange flere testklasser, fordi jeg trenger én ny klasse for hver Arrange/Given. Men det resulterer i en mye mere ryddig struktur – det er enkelt å finne frem i testene, og enkelt å se hva testene faktisk tester. Jeg vil også påstå at dette mønsteret gir meg mere stabile tester, med mindre koderepetisjon, og bedre muligheter for refakturering.

Noen flere eksempler

De to foregående eksemplene er klassiske enhetstester, hvor man korrelerer input og output fra en operasjon. Dette kan i teorien settes opp som en tabell, og man kunne kanskje brukt verktøy som Fit/FitNesse til å spesifisere det samme. Ofte bruker jeg i stedet det som akkurat nå kalles for “London school TDD” (er vel i bunn og grunn det vi har kalt BDD i noen år nå), hvor man tester samhandlingen mellom objekter. Da trenger vi mocks/test doubles, og det er da jeg bruker Moq-rammeverket. Denne formen for testing egner seg også godt til den teknikken jeg har beskrevet.

Her er en annen test fra meldings-DSL’en jeg lager. I denne testen setter jeg opp en semantisk modell som er det som er resultatet av at et DSL-skript har blitt kjørt. Jeg skal nå teste at når jeg “utfører” modellen så skal visse ting skje. Helt konkret sier jeg i SetupModel at programmet skal logge to tekstlinjer. Jeg skriver så to testmetoder; den ene kontrollerer at en av tekststrengene ble logget, og den andre tester at log-metoden ble kalt to ganger.

SetupModel er en tom, virtual metode i baseklassen, som vil bli kalt i setup.

 80 public class Executor_must_log : ServiceSemanticsExecutorTestFixture
 81 {
 82     protected override void SetupModel()
 83     {
 84         Model.Log.Add("This should be logged");
 85         Model.Log.Add("This should also be logged");
 86     }
 87 
 88     [Test]
 89     public void Must_log_spesific_log_text()
 90     {
 91         LogMock.Verify(log => log.Log("This should be logged"), Times.Once());
 92     }
 93 
 94     [Test]
 95     public void Must_log_all_items()
 96     {
 97         LogMock.Verify(log => log.Log(It.IsAny<string>()), Times.Exactly(2));
 98     }
 99 }

Det er baseklassen med det herlige navnet ServiceSemanticsExecutorTestFixture som eksekverer modellen, og som har mocks jeg så kan bruke til selve verifiseringen.

Og her følger et siste eksempel som er ganske interessant. Her tester jeg faktisk et testrammeverk! Jeg setter opp to modeller. Den ene jeg kaller Actual er den semantiske modellen fra en DSL. Expectations er den semantiske modellen fra en annen DSL som jeg bruker til å verifisere den første. I testmetoden kan jeg så kontrollere at testrammeverket jeg tester fanger opp missmatch mellom de to modellene, og at det rapporterer denne mismatchen korrekt.

104 public class Spec_ExpectedRepliesVerifier_fail : VerifierTestFixture<ExpectedRepliesVerifier>
105 {
106     protected override void SetupActual()
107     {
108         Actual.Replies.Add("This is a reply");
109     }
110 
111     protected override void SetupExpectations()
112     {
113         Expectations.ExpectedReplies.Add("This is a reply");
114         Expectations.ExpectedReplies.Add("This is another reply");
115     }
116 
117     [Test]
118     public void Should_report_missing_reply()
119     {
120         Result.Ok.ShouldBeFalse();
121         Result.Items.First().Exception.Message
122             .ShouldEqual("Missing reply: \"This is another reply\"");
123     }
124 }

Jeg bruker en ekstra teknikk i denne testen, og det er å bruke Generics til å spesifisere hvilken klasse jeg tester – nemlig ExpectedRepliesVerifier. Baseklassen oppretter en instans av denne, og bruker den til å validere de to modellene mot hverandre. Dermed kan jeg bruke denne samme teknikken på alle verifiseringsobjekter jeg lager, og for alle tenkelige oppsett av modeller.

Les også mine mest populære testrelaterte artikler: Utenfra-og-inn programmering og TDD og mocking i praksis.

Kodekata: Romertall

roman_numerals_kata

KataRomanNumerals er en kodekata beskrevet på codingdojo.org. Vanskelighetsgraden er enkel, men den kan være lærerik likevel. Her snakker jeg om hvordan jeg løste den i Clojure, og viser også en implementasjon i C#.

Oppgaven går rett og slett ut på å lage en metode som konverterer et tall fra titallssystemet til et romertall. Du kan stoppe når du når 3000, romerne hadde sjelden behov for å gå høyere enn det selv.

Dette er en typsik TDD-kata, som fungerer veldig bra om du ikke har peiling på hvordan algoritmen for å konvertere fra titallssystemet til romertall egentlig er. Bare begynn med tallet 1. Når du har en fungerende test som beviser at romertall(1) == "I" fortsetter du med å konvertere 2 til "II". Alt du trenger er en oversikt over romertall. Etterhvert vil du kunne se et mønster i koden du skriver for å få testene til å passere, og du kan gjøre en generalisering mot algoritmen du er ute etter.

Har du ikke implementert denne algoritmen før? Popp da opp din favoritt-editor og begynn å kode! Ikke fortsett med blogposten før du har løsningen – å lese videre vil bare gjøre deg forutinntatt, og det er langt fra sikkert min løsning er like elegant som det du kan komme opp med.

Trenger du et par “avanserte” testcases for å validere algoritmen kan du for eksempel bruke 1990 == MCMXC og 2008 == MMVIII.

Min løsning

Babysteps! Her ser du hvordan jeg startet kataen: Én test hvor jeg konverterer tallet 1, og sjekker at resultatet er “I”. TDD sier at jeg skal gjøre det enkleste jeg kan for å få testen til å passere, og det vil si å bare returnere “I” fra funksjonen min.

1 (ns marosoft.kata.roman-numerals
2     (:use clojure.test))
3
4 (defn number-to-roman [number]
5       “I”)
6
7 (deftest test-1
8          (is (= “I” (number-to-roman 1))))
9
10 (run-tests)

Og så bare fortsatte jeg. I kodeblokken nedenfor ser du hvordan det så ut etter noen minutters koding. For å spare meg for endel cut’n'paste valgte jeg å bare ha én test, som itererte over et sett med tester. Implementasjonen av number-to-roman er foreløbig ganske naiv; i bunn og grunn en lang “switch”.

Allerede på testcase 3 == "III" valgte jeg forresten å bruke rekursjon. I et språk som Clojure er det mye enklere enn å opprette og modifisere verdier i en loop, som kanskje hadde vært det jeg hadde brukt om jeg løste oppgaven i for eksempel C# eller Ruby.

1 (ns marosoft.kata.roman-numerals
2     (:use clojure.test))
3
4 (defn in-range [x a b]
5       (and (>= x a) (<= x b)))
6
7 (defn number-to-roman
8       ([number] (number-to-roman number “”))
9       ([number roman]
10        (if (zero? number) roman
11          (cond
12            (<= number 3) (recur
13                            (dec number) 
14                            (str roman “I”))
15            (= number 4) (str roman “IV”)
16            (in-range number 5 8) (recur
17                                    (- number 5)
18                                    (str roman “V”))
19            (= number 9) (str roman “IX”)
20            (in-range number 10 39) (recur
21                                      (- number 10)
22                                      (str roman “X”))
23            (in-range number 40 49) (recur
24                            (- number 40)
25                            (str roman “XL”))
26            (in-range number 50 59) (recur
27                                      (- number 50)
28                                      (str roman “L”))))))
29      
30 (def test-cases {
31       1 “I” 2 “II” 3 “III”
32       4 “IV” 5 “V” 6 “VI” 7 “VII” 8 “VIII”
33       9 “IX” 10 “X” 11 “XI” 12 “XII” 13 “XIII”
34       14 “XIV” 15 “XV” 16 “XVI” 17 “XVII” 18 “XVIII”
35       19 “XIX” 20 “XX” 21 “XXI”
36       30 “XXX” 38 “XXXVIII” 39 “XXXIX”
37       40 “XL” 41 “XLI” 45 “XLV” 49 “XLIX”
38       50 “L” 54 “LIV”
39       })
40
41 (defn test-convertion
42       [number expected]
43       (is (= (number-to-roman number) expected)))
44
45 (deftest test-all
46          (doseq [[num roman] test-cases]
47                 (test-convertion num roman)))
48
49 (run-tests)

Det var nå ikke lenger noe vanskelig å se et tydelig mønster i algoritmen, og dermed var det på tide å refakturere. Etter å ha gjort det, og lagt til resten av testene, stod jeg igjen med følgende implementasjon av Romertall-kataen:

1 (ns marosoft.kata.roman-numerals
2     (:use clojure.test))
3
4 (defn <-> [x a b] (and (>= x a) (<= x b)))
5
6 (defstruct <->-> :range-start :range-end :roman-suffix)
7
8 (def conversion-steps
9      [ (struct <->-> 1 3 "I") (struct <->-> 4 4 "IV")
10        (struct <->-> 5 8 "V") (struct <->-> 9 9 "IX")
11        (struct <->-> 10 39 "X") (struct <->-> 40 49 "XL")
12        (struct <->-> 50 89 "L") (struct <->-> 90 99 "XC")
13        (struct <->-> 100 399 "C") (struct <->-> 400 499 "CD")
14        (struct <->-> 500 899 "D") (struct <->-> 900 999 "DM")
15        (struct <->-> 1000 3999 "M") ])
16
17 (defn get-step [number]
18       (first (filter
19                #(<-> number (% :range-start) (% :range-end))
20                conversion-steps)))
21
22 (defn aggregate-roman [number roman]
23       (if (zero? number) 
24         roman
25         (let [step (get-step number)]
26           (recur
27             (- number (step :range-start))
28             (str roman (step :roman-suffix))))))
29
30 (defn number-to-roman [number]
31       (aggregate-roman number “”))
32
33 (deftest test-all
34          (doseq
35            [[num roman]
36             {
37             1 “I” 2 “II” 3 “III” 4 “IV” 5 “V” 6 “VI” 7 “VII” 8 “VIII”
38             9 “IX” 10 “X” 11 “XI” 12 “XII” 13 “XIII” 14 “XIV” 15 “XV”
39             16 “XVI” 17 “XVII” 18 “XVIII” 19 “XIX” 20 “XX” 21 “XXI”
40             30 “XXX” 38 “XXXVIII” 39 “XXXIX” 40 “XL” 41 “XLI” 45 “XLV” 
41             49 “XLIX” 50 “L” 54 “LIV” 60 “LX” 89 “LXXXIX” 90 “XC” 
42             96 “XCVI” 100 “C” 152 “CLII” 399 “CCCXCIX” 432 “CDXXXII” 
43             500 “D” 610 “DCX” 900 “DM” 1000 “M” 3000 “MMM” }]
44                 (is (= (number-to-roman num) roman))))
45
46 (run-tests)

Her har jeg byttet ut den lange “switchen” med en datastruktur (lookup-tabell om du vil) basert på StructMaps (som jeg presenterte i posten om algebraiske datatyper). For å være litt fancy gav jeg strukten et litt spesielt navn, nemlig <->->, som liksom skal bety at den definerer romertall-output "->" for en gitt range "<->" (jeg kalte også funksjonen for å sjekke om et tall er innenfor en range for <->). Geekpoints? ;)

Og så litt C#

Om du har litt vanskeligheter med å tyde Clojure-koden min har jeg kodet opp en C#-versjon, som så godt det lar seg gjøre bruker de samme teknikkene som Clojure-løsningen (bortsett fra at jeg har gjort den litt objektorientert ved å innføre en Roman-klasse). Linq-uttrykket i AggregateRoman-metoden tilsvarer for eksempel get-step-funksjonen i koden over.

1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using NUnit.Framework;
5
6 namespace Marosoft.Kata.Roman
7 {
8     [TestFixture]
9     public class RomanNumbersSpecification
10     {
11         [Test]
12         public void Test()
13         {
14             var testCases = new Dictionary<int, string>
15             {
16         {1, “I”}, {2, “II”}, {3, “III”}, {4, “IV”}, {5, “V”}, {6, “VI”}, {7, “VII”}, {8, “VIII”},
17         {9, “IX”}, {10, “X”}, {11, “XI”}, {12, “XII”}, {13, “XIII”}, {14, “XIV”}, {15, “XV”},
18         {16, “XVI”}, {17, “XVII”}, {18, “XVIII”}, {19, “XIX”}, {20, “XX”}, {21, “XXI”},
19         {30, “XXX”}, {38, “XXXVIII”}, {39, “XXXIX”}, {40, “XL”}, {41, “XLI”}, {45, “XLV”},
20         {49, “XLIX”}, {50, “L”}, {54, “LIV”}, {60, “LX”}, {89, “LXXXIX”}, {90, “XC”},
21         {96, “XCVI”}, {100, “C”}, {152, “CLII”}, {399, “CCCXCIX”}, {432, “CDXXXII”},
22         {500, “D”}, {610, “DCX”}, {900, “DM”}, {1000, “M”}, {3000, “MMM”},
23             };
24
25             foreach (var item in testCases)
26                 new Roman(item.Key).StringRepresentation
27                     .ShouldEqual(item.Value);
28         }
29     }
30
31     public class Roman
32     {
33         class ConversionStep
34         {
35             internal int RangeStart;
36             internal int RangeEnd;
37             internal string RomanSuffix;
38         }
39
40         private static List<ConversionStep> _conversionSteps = new List<ConversionStep>
41         {
42             new ConversionStep {  RangeStart = 1, RangeEnd = 3, RomanSuffix = “I” },
43             new ConversionStep {  RangeStart = 4, RangeEnd = 4, RomanSuffix = “IV” },
44             new ConversionStep {  RangeStart = 5, RangeEnd = 8, RomanSuffix = “V” },
45             new ConversionStep {  RangeStart = 9, RangeEnd = 9, RomanSuffix = “IX” },
46             new ConversionStep {  RangeStart = 10, RangeEnd = 39, RomanSuffix = “X” },
47             new ConversionStep {  RangeStart = 40, RangeEnd = 49, RomanSuffix = “XL” },
48             new ConversionStep {  RangeStart = 50, RangeEnd = 89, RomanSuffix = “L” },
49             new ConversionStep {  RangeStart = 90, RangeEnd = 99, RomanSuffix = “XC” },
50             new ConversionStep {  RangeStart = 100, RangeEnd = 399, RomanSuffix = “C” },
51             new ConversionStep {  RangeStart = 400, RangeEnd = 499, RomanSuffix = “CD” },
52             new ConversionStep {  RangeStart = 500, RangeEnd = 899, RomanSuffix = “D” },
53             new ConversionStep {  RangeStart = 900, RangeEnd = 999, RomanSuffix = “DM” },
54             new ConversionStep {  RangeStart = 1000, RangeEnd = 3999, RomanSuffix = “M” },
55         };
56
57         public Roman(int number)
58         {
59             Number = number;
60             StringRepresentation = AggregateRoman(Number, string.Empty);
61         }
62
63         public int Number { get; private set; }
64         public string StringRepresentation { get; private set; }
65        
66         private string AggregateRoman(int number, string roman)
67         {
68             if (number == 0)
69                 return roman;
70
71             var step = (from ConversionStep cs in _conversionSteps
72                        where cs.RangeStart <= number && cs.RangeEnd >= number
73                        select cs).Single();
74
75             return AggregateRoman(number – step.RangeStart, roman + step.RomanSuffix);
76         }
77     }
78 }

Løsningen jeg kom opp med er som antydet tidligere nok ikke den mest optimale. For en mere grundig gjennomgang av problemet og en “slimmere” løsning i C# kan du ta en titt på denne linken.

Enhetstester i Clojure

Da jeg presenterte kassaapparat-kataen i clojure sa jeg at jeg ikke la vekt på enhetstester. Det betyr derimot ikke at jeg ikke skrev tester i det hele tatt.

Clojure kommer med et eget namespace for å skrive enhetstester, og fornuftig nok heter det clojure.test. Tidligere var dette kun en del av clojure-contrib, men det har nå blitt tatt opp i selve Clojure. For å ta det i bruk benytter jeg først ns-funksjonen til å definere et namespace i filen min, og kan der inkludere avhengigheter jeg ønsker å benytte, slik som dette:

  1 (ns marosoft.kata.cash-register
  2     (:use clojure.test))

For å skrive enhetstestene er det så kun to ting du trenger å vite om: Du definerer en test ved hjelp av deftest. En test ser ut som en vanlig funksjon, men uten parametre. Når du skal gjøre en “assert” i testen bruker du makroen is. Hvis argumentet til is ikke evaluerer til true vil testen feile.

Her er testene jeg skrev for basisfunkjonaliteten i kassaapparat-kataen:

106 ; — TESTS —
107
108 (deftest test-that-price-returns-correct-values
109          (is (= 3.99 (price “bread”)))
110          (is (= 2.50 (price “milk”)))
111          (is (= 4.00 (price “butter”))))
112
113 (deftest price-for-2-milk
114          (is (= (* 2.50 2)
115                 (price-for-selection “milk” 2))))
116
117 (deftest price-for-3-bread-using-overload
118          (is (= (* 3.99 3)
119                 (price-for-selection ["bread" 3]))))
120
121 (deftest total-of-a-bread-2-milk-3-butter
122          (is (= (+ 3.99 (* 2 2.50) (* 3 4.00))
123                 (total [["bread" 1]["milk" 2]["butter" 3]]))))
124
125 (deftest buy-will-update-cart
126          (let [original-cart
127                 [["bread" 2]]
128                 updated-cart
129                 (buy “butter” 1 original-cart)]
130            (is (= 1 (count original-cart)))
131            (is (= 2 (count updated-cart)))
132            (is (= 1 (count (filter
133                              #(= (first %) “butter”) 
134                              updated-cart))))))
135
136 (deftest cart-will-not-be-cleared-if-not-enough-money
137          (let [updated-cart (checkout 10.0 [["milk" 10]])]
138            (is (= 1 (count updated-cart)))))
139
140 (deftest cart-will-be-cleared-if-enough-money
141          (let [updated-cart (checkout 100.0 [["milk" 10]])]
142            (is (= 0 (count updated-cart)))))
143

For å kjøre testene kaller du den innebygde funkjonen run-tests ved for eksempel å skrive (run-tests) i bunnen av fila. Da jeg implementerte kataen min gjorde jeg det derimot litt anderledes…

Integrere testene som en del av programmet

Da jeg implementerte kassaapparatet skrev jeg testene i samme fil som selve programmet. Jeg utvidet så brukerens meny til å inkludere en opsjon for å kjøre alle testene. Dette gjorde jeg ved å legge linjen nedenfor til i dispatch-command listen (se forrige blogpost).

72      “test” (fn [state] (do (run-tests) (System/exit 0)))

Jeg kunne dermed starte programmet og skrive ordet “test” for å kjøre testene. Jeg valgte også å la programmet avslutte etter at testene var kjørt ved å eksekvere (System/exit 0), som er Java interop og tilsvarer å kalle System.exit(0).

Kult?

Flere muligheter

is-makroen kan ta en ekstra streng-parameter som beskriver hva som testes, om du liker å gjøre det. I tillegg finnes det en testing-makro som lar deg definere mere beskrivende tester, ala BDD / RSpec.

Man kan også definere test fixtures, som i praksis gir deg setup (before) og teardown (after) logikk. Og man kan komponere ulike sett av tester, og spesifisere hvilke namespace man ønsker å kjøre tester for når man kaller run-tests.

Og man kan definere en funksjon og testene for funksjonen i par, slik at det ikke er noen avstand mellom testen og koden. Bruker man dette vil man nok tvinge seg selv til å gjennomføre ganske god TDD. Man kan også sette et flagg som dropper testene under kompilering, slik at det å ha testene sammen med produksjonkoden ikke er noen issue.

Her har jeg laget en funksjon som er definert sammen med testene sine, og hvor jeg også bruker testing-makroen for å gruppere (det gir også bedre rapportering ved feil):

146 (with-test
147   (defn can-watch-movie?
148         “Can person watch movie based on MPAA rating?”
149         ([rating age] ; overload 1 (defaults to no adult supervision)
150          (can-watch-movie? rating age false))
151         ([rating age with-adult?] ; overload 2..
152          (condp = rating
153                 “R” (or (>= age 17) with-adult?)
154                 “NC-17″ (> age 17)
155                 true))) ; defaults to true for all other ratings
156   (testing “Non-restrictive ratings”
157            (is (true? (can-watch-movie? “G” 1 false)))
158            (is (true? (can-watch-movie? “PG” 2 false)))
159            (is (true? (can-watch-movie? “PG-13″ 12 false))))
160   (testing “Restricted rating (adult guardian required if under 17)”
161            (is (false? (can-watch-movie? “R” 16 false)))
162            (is (true? (can-watch-movie? “R” 16 true)))         
163            (is (true? (can-watch-movie? “R” 17 false))))
164   (testing “NC-17 – No One 17 and Under Admitted”
165            (is (false? (can-watch-movie? “NC-17″ 17 false)))
166            (is (false? (can-watch-movie? “NC-17″ 17 true)))
167            (is (true? (can-watch-movie? “NC-17″ 18 false))))
168   (testing “Using overload without specifying supervision”
169            (is (false? (can-watch-movie? “R” 16)))))

For en fullstendig oversikt over hva du har tilgjengelig kan du ta en titt på clojure.test API-referansen.

Lese log4net-loggen i enhetstester

Av og til har jeg behov for å sjekke hva som logges til log4net i enhets- eller integrasjonstester. Dette er ikke “best practise”, man bør normalt kunne verifisere forventet adferd på andre måter, men det finnes alltid unntak. I vår test-suite på rundt 700 tester har dette behoved dukket opp en håndfull steder.

Til dette formålet har jeg kommet opp med klassen nedenfor. C#-koden konfigurerer opp log4net med en appender som legger alt som logges til en List of LoggingEvent. For enkelhets skyld, siden jeg allerede normalt har en avhengighet til mockingrammeverket Moq, bruker jeg Moq til å opprette en appender for meg. Om du ikke bruker noe tilsvarende kan du selvsagt lage din egen in-memory appender.

    1 using System;
    2 using System.Collections.Generic;
    3 using Moq;
    4 using log4net.Appender;
    5 using log4net.Core;
    6 using log4net.Config;
    7 
    8 namespace SharedTestLib
    9 {
   10     /// <summary>
   11     /// Will capture all events logged through log4net 
   12     /// for investigation in automated tests
   13     /// </summary>
   14     public static class TestLog
   15     {
   16         private static bool _initialized;
   17         private static List<LoggingEvent> _log_events = new List<LoggingEvent>();
   18 
   19         /// <summary>
   20         /// FakeLog will start to capture log events. 
   21         /// Call it in TestFixtureSetUp.
   22         /// </summary>
   23         public static void Initialize()
   24         {
   25             if (_initialized)
   26                 return;
   27 
   28             var mockAppender = new Mock<IAppender>();
   29             mockAppender
   30                 .Setup(appender => appender.DoAppend(It.IsAny<LoggingEvent>()))
   31                 .Callback((LoggingEvent le) => _log_events.Add(le));
   32             BasicConfigurator.Configure(mockAppender.Object);
   33             _initialized = true;
   34         }
   35 
   36         /// <summary>
   37         /// Clears the events.
   38         /// Call it in SetUp.
   39         /// </summary>
   40         public static void Clear()
   41         {
   42             _log_events = new List<LoggingEvent>();
   43         }
   44 
   45         public static IEnumerable<LoggingEvent> Events
   46         {
   47             get
   48             {
   49                 return _log_events;
   50             }
   51         }
   52     }
   53 }

Det er ikke ofte jeg lager statiske klasser, men i dette tilfellet var det helt greit, og den blir veldig enkel å bruke i enhetstestene. Jeg valgte å implementere en Initialize()-metode for eksplisit initialisering, men en statisk konstruktør kunne nok gjort samme nytten. Initialize kan forøvrig (som du ser) kalles flere ganger, appenderen blir bare lagt til én gang.

Jeg er forresten klar over at log4net også har en MemoryAppender jeg kunne ha brukt, men min fremgangsmåte virket noe enklere sammenlignet med det jeg fant om den på nettet.

Videre kan du inkludere noen properties for å hente ut siste event av en bestemt type – det er ikke usannsynlig at det er akkurat det du er ute etter:

   55 #region Convenience methods
   56 
   57 public static LoggingEvent LastEvent { get { return _log_events.Last(); } }
   58 
   59 public static LoggingEvent LastDebug { get { return Last(Level.Debug); } }
   60 public static LoggingEvent LastInfo { get { return Last(Level.Info); } }
   61 public static LoggingEvent LastWarn { get { return Last(Level.Warn); } }
   62 public static LoggingEvent LastError { get { return Last(Level.Error); } }
   63 public static LoggingEvent LastFatal { get { return Last(Level.Fatal); } }
   64 
   65 private static LoggingEvent Last(Level level)
   66 {
   67     return _log_events.Where(e => e.Level == level).Last();
   68 }
   69 
   70 #endregion

Merk at du må inkludere System.Linq for å få tilgang til metodene Where() og Last().

For å gjøre testloggen enda enklere og mere beskrivende å bruke i test-sammenheng, har jeg lagt til noen funksjonelle godbiter for å gjøre verifiseringer. Den første metoden verifiserer at alle eventene i loggen tilfredstiller et gitt predikat, den andre at minst ett event gjør det (First() kaster et Exception om ingen matcher). Disse metodene har navn som passer inn med testmetodene fra coretdd som jeg alltid bruker.

   72 #region Asserts
   73 
   74 /// <summary>
   75 /// All events in log must satisfy the predicate
   76 /// </summary>
   77 public static void ShouldSatisfy(Predicate<LoggingEvent> predicate)
   78 {
   79     _log_events.ForEach(e => 
   80     {
   81         if (!predicate(e))
   82             Assert.Fail(
   83                 "Event does not satisfy predicate: {0} - {1}", 
   84                 e.Level, 
   85                 e.RenderedMessage);
   86     });
   87 }
   88 
   89 public static void ShouldContain(Func<LoggingEvent, bool> predicate)
   90 {
   91     _log_events.First(predicate);
   92 }
   93 
   94 #endregion

Merk at du må legge til en referanse til NUnit for bruken av Assert.Fail() i ShouldSatisfy-metoden.

Her er et par eksempler på hvordan disse siste metodene kan brukes:

   89 private void log_should_not_contain_any_fatal_events()
   90 {
   91     TestLog.ShouldSatisfy(logEvent => logEvent.Level != Level.Fatal);
   92 }
   93 
   94 public void log_should_contain_text(string value)
   95 {
   96     TestLog.ShouldContain(logEvent => logEvent.RenderedMessage.Equals(value));
   97 }

Håper dette kan være til nytte for noen. Lykke til med testingen!

Tester for asynkron kode

Lenge siden jeg har hatt noe C#-kode på bloggen nå.., la meg gjøre noe med det. På jobben i forrige uke lagde vi to små extension methods jeg har lyst til å dele med verden. Det er ikke noe rocket science, men kan være greit om du har samme behovet som oss, og du ikke har tenkt på det selv.

Systemene vi jobber med inneholder MYE asynkronitet / samtidighet. Når vi skal lage automatiserte tester – spesielt integrasjonstester – må vi forholde oss til at vi ikke øyeblikkelig får det resultatet vi forventer (eventual consistency). En test kan f.eks. sende en melding som plukkes opp og behandles av en annen tråd. Dette fører til endel kompleksitet i testene; ofte legger vi inn en sleep før vi kontrollerer resultatet.

Her er en tenkt test hvor vi har en publisher og en subscriber. Vi publiserer en melding, og kontrollerer at subscriberen har mottatt:

 

2 [TestFixture]
3 public class When_publishing_a_message
4 {
5   [Test]
6   public void Subscriber_should_receive_within_reasonable_time()
7   {
8     When.publisher.distribute(a_message);
9     Thread.Sleep(2_seconds); // 2 sec seems to be the worst case
10     Then.subscriber.ReceivedMessageCount.ShouldEqual(1);
11   }
12
13   private Publisher publisher;
14   private Subscriber subscriber;
15   private Message a_message;
16   private const int 2_seconds = 2000;
17
18   /* Setup, Teardown and BDD snippet not shown */
19 }

Ett av problemene med dette er at faktorer utenfor vår kontroll kan føre til at tiden vi må vente av og til øker ganske mye. Vi bruker da endel tid på å finne den optimale ventetiden, for vi vil ikke at testene skal bruke for lang tid. Men fra tid til annen feiler likevel en og annen test pga. stor belastning på bygg-serveren. Dette er ikke bra – vi må kunne stole på stabiliteten på builden!

For å bedre dette har vi kommet opp med en abstraksjon vi kan bruke i stedet for en hardkodet sleep. Disse extensionmetodene sjekker om et predikat er tilfredstilt helt til det er det, eller til vi har nådd en timeout-verdi:

 

1 public static class AsyncTestExtensions
2 {
3   public static void ShouldBeTrueWithin(
4       this Func<bool> predicate, int milliseconds)
5   {
6     DateTime timeout = DateTime.Now.AddMilliseconds(milliseconds);
7     while (DateTime.Now < timeout)
8     {
9       if (predicate()) return;
10       Thread.Sleep(10);
11     }
12     Assert.Fail();
13   }
14  
15   public static void ShouldBeFalseWithin(
16       this Func<bool> predicate, int milliseconds)
17   {
18     ShouldBeTrueWithin(() => !predicate(), milliseconds);
19   }
20 }

Med disse på plass kan vi skive om testen slik:

 

22 [TestFixture]
23 public class When_publishing_a_message
24 {
25   [Test]
26   public void Subscriber_should_receive_within_reasonable_time()
27   {
28     When.publisher.distribute(a_message);
29     Then.subscriber_have_received_a_message.ShouldBeTrueWithin(2_seconds);
30   }
31
32   private Func<bool> subscriber_have_received_a_message = () =>
33     subscriber.ReceivedMessageCount.Equals(1);
34
35   /* Fields, Setup, Teardown and BDD snippet not shown */
36   /* Note : subscriber now needs to be static          */
37 }

Testen er nå mye mere lesbar (i min mening). I tillegg kjører testene gjevnt over raskere, men uten å feile de gangene maskinen trenger litt mere tid på å gjøre seg ferdig.

PS: When og Then-nøkkelordene i testene stammer fra min Ultra-tiny given-when-then DSL-snippet. ShouldEqual-metoden i den første versjonen av testen kommer fra coreTDD, som vi bruker i alle tester i stedet for vanlige nUnit asserts.

En agurktest

Om du ikke har levd under en stein det siste året så har sansynligvis hørt om Aslak Hellesøys Cucumber.

“Cucumber lets software development teams describe how software should behave in plain text. The text is written in a business-readable domain-specific language and serves as documentation, automated tests and development-aid – all rolled into one format.

“Cucumber works with Ruby, Java, .NET, Flex or web applications written in any language. It has been translated to over 30 spoken languages.

Men du har kanskje ikke testet det selv? Jeg tenkte derfor jeg kunne gjøre en rask demonstrasjon ved å skrive en akseptansetest for tjenesten jeg lagde i blogpostene om en minimal http-server i .Net / Ruby. Normalt vil man selvsagt beskrive featurene først, og så implementere dem og bruke cucumber til å bevise at de fungerer, men for enkelhets skyld tester jeg her altså min eksisterende tjeneste. Det spiller forøvrig ingen rolle om jeg tester .Net-varianten eller Ruby-varianten – jeg vil la akseptansetesten aksessere tjenesten direkte over http, og da er jo grensesnittet uansett det samme.

Det første jeg må gjøre er å beskrive tjenesten i form av en feature med tilhørende scenarier. Virker dette litt gresk anbefaler jeg at du leser deg litt opp på Behaviour-Driven Development (BDD).

File: features\add_service.feature:
1 Feature: Add-service
2   In order to save cycles on client CPUs
3   As an IT manager
4   I want a central server that can add numbers
5
6   Scenario: Add a string of numbers
7     When I send 9,11,33,100 to the add service
8     Then the response should be “The answer is 153″
9
10   Scenario: Add a single number
11     When I send 1 to the add service
12     Then the response should be “The answer is 1″

Feature-filen er input til cucumber, som vil analysere stegene i scenariene og forsøke å eksekvere dem. Men foreløpig skjønner cucumber lite, så vi må skrive litt kode for å definere stegene.

File: features\add_service_steps.rb:
1 require open-uri
2 require spec/expectations
3
4 When /I send (.*) to the (.*) service/ do |argument, service|
5   open(http://127.0.0.1:4567/#{service}?#{argument}) do |response|
6     @data = response.read
7   end
8 end
9
10 Then /the response should be “([^\"]*)/ do |expected|
11   @data.should == expected
12 end

Sånn, det er alt som trengs. Jeg lager et “When I send X to the Y service”-steg som gjør en forespørsel til tjenesten min og sender de definerte argumentene. Cucumber tar seg av å bytte ut X og Y med argumentene og navnet på tjenesten. “Then the response should be Z”-steget gjør en assert mot det forventede svaret som er beskrevet i scenariene.

Legg merke til at det er to forskjellige DSL’er (domenespesifike språk) i bruk her – en ekstern DSL for å definere features, og en intern Ruby-DSL (intern som i “er gyldig ruby-kode) for å definere stegene.

Når jeg kjører cucumber nå får jeg følgende output:

C:\Users\tormar\ruby_projects\httpListener>cucumber
Feature: Add-service
        In order to save cycles on client CPUs
        As an IT manager
        I want a central server that can add numbers

  Scenario: Add a string of numbers
    When I send 9,11,33,100 to the add service
    Then the response should be “The answer is 153

  Scenario: Add a single number
    When I send 1 to the add service
    Then the response should be “The answer is 1

2 scenarios (2 passed)
4 steps (4 passed)
0m0.122s

Alle scenariene for Add-servicen passerte (hvis ikke hadde det dukket opp endel rødt i output). Kult?

Hvis du er alergisk mot Ruby så finnes det også et prosjekt som heter SpecFlow som er en port av cucumber til .Net. Men det er absolutt å anbefale å benytte the real thing, gjerne med IronRuby på .Net-plattformen.


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