Unit-tester er nyttig for de fleste systemutviklere.
Noen av oss kjører strikt, metodisk testdrevet utvikling. Andre bruker bare automatiserte tester nå og da som sikkerhetsnett for å unngå regress-feil. Hvor ofte fyrer du selv av testene dine? Kjører du testsuiten din en gang i ny og ne, eller strikt for hver metode du implementerer?
Jeg liker å kjøre testene mine ofte mens jeg arbeider. Det jeg liker enda bedre er å la utviklingsmiljøet mitt kjøre dem for meg, automatisk. Poenget med yrket vårt er jo nettopp å automatisere og effektivisere arbeidsprosesser – dette forsøker jeg å gjøre også med mine egne rutiner og verktøy. La oss se hvordan vi kan få til dette.
Mange av oss bruker allerede Continuous Integration for å oppdage feil i den felles kodebasen. Personlig autotesting er det neste, logiske steget: enhetstestene våre burde kjøre i bakgrunnen hver gang vi endrer noe, og gi oss umiddelbar tilbakemelding når vi knekker funksjonalitet. På denne måten oppdager vi våre egne feil før resten av teamet blir påvirket av dem.
Det finnes flere verktøy som gjør dette for deg:
Det er imidlertid ikke vanskelig å lage et enkelt autotest-verktøy på egen hånd. Jeg synes at det er en god ide å bruke litt tid her og der på å forbedre sitt eget utviklingsmiljø. Vi har jo muligheten til å lage våre egne verktøy (smeden er en av få håndverkere som kan si det samme). Som utvikler bør man iallfall klare å slipe sine egne kniver, for å si det sånn. Og dette er en god anledning!
Et naivt script for autotesting implementerer du på en ettermiddag. Alt du trenger er et lite program som reagerer på endringer i prosjektfilene dine, og kjører testene etter behov.
Algoritmen er enkel: La scriptet kjøre regelmessig, f.eks hvert sekund. Kontroller “forrige endring”-klokkeslettet for alle filene i prosjektet. Dersom klokkeslettet på en eller flere filer har endret seg siden forrige kontroll, så kjør enhetstestene til prosjektet. Analyser output fra testkjøringen, og se hvorvidt noen tester feilet eller kode knakk helt (exceptions). Gi så klar tilbakemelding om testene er ok eller feilet.
Tilbakemeldingen kan være litt grafikk og lyd på desktopen (for eksempel med Growl eller Snarl), en lavalampe på pulten, blinkende LED-lamper på laptopen… vær kreativ! Bare sørg for er at du får en tydelig tilbakemelding på hvorvidt testene feiler eller ikke.
Filmklippet under viser hvordan mitt eget autotest-miljø fungerer. Koden er i dette tilfellet skrevet i et språk som heter Clojure og ser derfor kanskje litt pussig ut, men ikke heng deg opp i det: det som demonstreres er at jeg knekker og fikser tester, og får automatisk godt synlig feedback på dette underveis.
Ikke spesielt komplisert.
“Dette ser jo genialt ut! Hvorfor bruker ikke alle autotest-verktøy allerede?”
Vel, det finnes dessverre noen utfordringer.
Dersom autotesting skal fungere hensiktsmessig må testsuiten som kjøres kun bestå av raske enhetstester, ikke tunge integrasjonstester. Og enhetstestene må gå kjapt! En test-run må ta sekunder, ikke minutter, ellers mister du gevinsten ved den umiddelbare feedbacken.
Problemet med trege tester er betydelig. Det finnes workarounds: man kan analysere avhengigheter mellom tester og kode, kun kjøre de berørte testene, kjøre raske tester først, feilende tester først, nylig feilende tester først… Men mange av oss har dessverre erfart at det er vanskelig å få til et godt fungerende opplegg for dette i et vanlig moderat stort utviklingsprosjekt.
Så er ikke autotesting nyttig likevel, da? Jo! Autotesting fungerer knallbra i små prosjekter (som ikke har samlet på seg så mye kode ennå) og egenopplæring (når du lærer nye verktøy og språk og ønsker å ta små steg med kjapp feedback). Det kan kanskje også fungere i større prosjekter dersom du har muligheten til å segmentere koden og testene dine i mindre moduler på en hensiktsmessig måte.
I bunnen av denne artikkelen har jeg vedlagt et eksempel på hvordan et enkelt autotest-script kan se ut, implementert i Ruby. Scriptet er skrevet for OS X-miljø, men koden bør være relativt lesbar og enkel å porte til ditt eget favorittspråk og utviklingsmiljø.
Happy testing!
Eksempel:
# Command line command which launches all unit tests RUN_TESTS_CMD = "mvn clean test" # Pick up any changes in files matching this filepath FILES_TO_MONITOR = "**/*.java" # Determine test outcome by scraping test output for #telltale signs of failure or exceptions
def test_failed?(test_output)
return (test_output["FAIL in"] != nil) # Any 'FAIL in' strings in test result indicates failure
end
def exception_occurred?(test_output)
return (test_output["EXCEPTION in"] != nil)
end
# Indicate test state by turning the background color of the test terminal
# a different color - yellow red og green. Currently uses Mac AppleScripts, but could be
# an audible signal, lava lamp, or anything else instead.
def indicate_tests_running
`osascript bin/autotest/make-term-yellow`
puts "\n\n\n----------------------------------"
puts "TESTRUN STARTED #{Time.now}"
puts "----------------------------------\n\n\n"
end
def indicate_test_success(description)
`osascript bin/autotest/make-term-green`
puts description
end
def indicate_test_errors(description)
`osascript bin/autotest/make-term-red`
puts description
end
$monitored_files = {} # Stores the last changed time of each file
def files_changed? # Check if any files have been touched/saved since last check
file_changed = false;
Dir[FILES_TO_MONITOR].each do |filepath|
if(!File.new(filepath).ctime.eql?($monitored_files[filepath]))
file_changed = true;
$monitored_files[filepath] = File.new(filepath).ctime
end
end
return file_changed
end
def run_test
indicate_tests_running
result = `#{RUN_TESTS_CMD}` #Run the tests, pipe resulting console output back
display_results(result)
end
def display_results(testOutput)
if test_failed?(testOutput)
indicate_test_errors("SOME TEST(S) FAILED\n"+testOutput)
elsif exception_occurred?(testOutput)
indicate_test_errors("EXCEPTIONS OCCURRED\n"+testOutput)
else
indicate_test_success("ALL TESTS SUCCEED\n"+testOutput)
end
end
def test_loop
while true
if files_changed?
run_test
end
sleep(1) #seconds between each poll for file changes
end
end
test_loop # Start testing!
Johannes Brodwall
Thomas Kjeldahl Nilsson
Sten Morten
Skriv en kommentar