torsdag 1. juli 2010 Clojure Testing
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:
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:
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…
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).
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?
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):
For en fullstendig oversikt over hva du har tilgjengelig kan du ta en titt på clojure.test API-referansen.