Jeg vet at en måte å gjøre det på kan være:
@Test
public void foo(){
try{
//execute code that you expect not to throw Exceptions.
}
catch(Exception e){
fail("Should not have thrown any exception");
}
}
Er det noen renere måte å gjøre dette på. (Sannsynligvis ved hjelp av Junit' s @Rule
?)
Du angriper dette på feil måte. Bare test funksjonaliteten din: Hvis det oppstår et unntak, mislykkes testen automatisk. Hvis det ikke kastes noe unntak, blir alle testene dine grønne.
Jeg har lagt merke til at dette spørsmålet vekker interesse fra tid til annen, så jeg skal utdype det litt.
Når du enhetstester, er det viktig å definere for deg selv hva du anser som en arbeidsenhet. I utgangspunktet: et utdrag av kodebasen din som kan inneholde flere metoder eller klasser som representerer en enkelt funksjonalitet.
Eller som definert i The art of Unit Testing, 2nd Edition av Roy Osherove, side 11:
En enhetstest er et automatisert stykke kode som påkaller arbeidsenheten som testes, og deretter sjekker noen antagelser om et enkelt sluttresultat av denne enheten. En enhetstest skrives nesten alltid ved hjelp av et rammeverk for enhetstesting. Den kan skrives enkelt og kjøres raskt. Den er pålitelig, lesbar og vedlikeholdbar. Resultatene er konsistente så lenge produksjonskoden ikke er endret.
Det som er viktig å være klar over, er at en arbeidsenhet vanligvis ikke bare er én metode, men at den på et helt grunnleggende nivå er én metode som deretter kapsles inn av andre arbeidsenheter.
Skriv inn bildebeskrivelse her]2.
Ideelt sett bør du ha en testmetode for hver enkelt arbeidsenhet, slik at du alltid umiddelbart kan se hvor ting går galt. I dette eksemplet er det en grunnleggende metode kalt getUserById()
som returnerer en bruker, og det er totalt 3 arbeidsenheter.
Den første arbeidsenheten skal teste om det returneres en gyldig bruker ved gyldig og ugyldig input.
Eventuelle unntak som kastes av datakilden, må håndteres her: Hvis det ikke finnes noen bruker, bør det være en test som viser at det kastes et unntak når brukeren ikke finnes. Et eksempel på dette kan være IllegalArgumentException
som fanges opp med annotasjonen @Test(expected = IllegalArgumentException.class)
.
Når du har håndtert alle brukstilfellene for denne grunnleggende arbeidsenheten, går du opp et nivå. Her gjør du nøyaktig det samme, men du håndterer bare unntakene som kommer fra nivået rett under det aktuelle. På denne måten holder du testkoden godt strukturert og kan raskt gå gjennom arkitekturen for å finne ut hvor ting går galt, i stedet for å måtte hoppe over alt.
Nå bør det være klart hvordan vi skal håndtere disse unntakene. Det finnes to typer input: gyldige inndata og feilaktige inndata (inndataene er gyldige i streng forstand, men de er ikke korrekte).
Når du arbeider med gyldig input, forventer du implisitt at uansett hvilken test du skriver, vil den fungere.
Et slikt metodekall kan se slik ut: existingUserById_ShouldReturn_UserObject
. Hvis denne metoden mislykkes (f.eks. hvis det kastes et unntak), vet du at noe gikk galt, og du kan begynne å grave.
Ved å legge til en annen test (nonExistingUserById_ShouldThrow_IllegalArgumentException
) som bruker feil input og forventer et unntak, kan du se om metoden din gjør det den skal med feil input.
Du prøvde å gjøre to ting i testen din: sjekke for gyldig og feil input. Ved å dele dette opp i to metoder som gjør én ting hver, får du mye klarere tester og en mye bedre oversikt over hvor ting går galt.
Ved å ha den lagdelte arbeidsenheten i bakhodet kan du også redusere mengden tester du trenger for et lag som er høyere i hierarkiet, fordi du ikke trenger å ta hensyn til alt som kan ha gått galt i de lavere lagene: Lagene under det aktuelle laget er en virtuell garanti for at avhengighetene dine fungerer, og hvis noe går galt, er det i det aktuelle laget (forutsatt at de lavere lagene ikke kaster noen feil selv).
Hvis du er uheldig nok til å fange opp alle feil i koden din. Du kan dumt nok gjøre
class DumpTest {
Exception ex;
@Test
public void testWhatEver() {
try {
thisShouldThroughError();
} catch (Exception e) {
ex = e;
}
assertEquals(null,ex);
}
}
Hvis du vil teste om testmålet ditt bruker unntaket. La bare testen være som (mock collaborator ved hjelp av jMock2):
@Test
public void consumesAndLogsExceptions() throws Exception {
context.checking(new Expectations() {
{
oneOf(collaborator).doSth();
will(throwException(new NullPointerException()));
}
});
target.doSth();
}
Testen vil bestå hvis testobjektet konsumerer unntaket som kastes, ellers vil testen mislykkes.
Hvis du vil teste logikken for forbruk av unntak, blir det mer komplisert. Jeg foreslår at du delegerer forbruket til en kollaboratør som kan simuleres. Testen kan derfor se slik ut:
@Test
public void consumesAndLogsExceptions() throws Exception {
Exception e = new NullPointerException();
context.checking(new Expectations() {
{
allowing(collaborator).doSth();
will(throwException(e));
oneOf(consumer).consume(e);
}
});
target.doSth();
}
Men noen ganger er det overdesignet hvis du bare vil logge det. I så fall kan denne artikkelen (http://java.dzone.com/articles/monitoring-declarative-transac, http://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-there-an-easy-way/) være til hjelp hvis du insisterer på tdd i dette tilfellet.