Ich weiß, dass eine Möglichkeit darin besteht, dies zu tun:
@Test
public void foo(){
try{
//execute code that you expect not to throw Exceptions.
}
catch(Exception e){
fail("Should not have thrown any exception");
}
}
Gibt es eine sauberere Möglichkeit, dies zu tun. (Wahrscheinlich mit Junit's @Rule
?)
Sie gehen die Sache falsch an. Testen Sie einfach Ihre Funktionalität: Wenn eine Ausnahme ausgelöst wird, schlägt der Test automatisch fehl. Wenn keine Ausnahme ausgelöst wird, werden Ihre Tests alle grün angezeigt.
Ich habe festgestellt, dass diese Frage von Zeit zu Zeit auf Interesse stößt, also werde ich sie ein wenig erweitern.
Wenn Sie Unit-Tests durchführen, ist es wichtig, für sich selbst zu definieren, was Sie als eine Arbeitseinheit betrachten. Grundsätzlich: ein Auszug aus Ihrer Codebasis, der mehrere Methoden oder Klassen enthalten kann oder auch nicht, die eine einzelne Funktionalität darstellen.
Oder, wie in The art of Unit Testing, 2nd Edition von Roy Osherove, Seite 11 definiert:
Ein Unit-Test ist ein automatisiertes Stück Code, das die zu testende Arbeitseinheit aufruft und dann einige Annahmen über ein einzelnes Endergebnis dieser Einheit überprüft. Ein Unit-Test wird fast immer mit Hilfe eines Unit-Test-Frameworks geschrieben. Er kann einfach geschrieben werden und läuft schnell. Er ist vertrauenswürdig, lesbar und wartbar. Er ist konsistent in seinen Ergebnissen, solange der Produktionscode nicht geändert wurde.
Es ist wichtig zu erkennen, dass eine Arbeitseinheit in der Regel nicht nur eine Methode ist, sondern auf einer sehr grundlegenden Ebene eine Methode, die dann von anderen Arbeitseinheiten gekapselt wird.
Idealerweise sollten Sie für jede einzelne Arbeitseinheit eine Testmethode haben, damit Sie immer sofort sehen können, wo etwas schief läuft. In diesem Beispiel gibt es eine Basismethode namens getUserById()
, die einen Benutzer zurückgibt, und es gibt insgesamt 3 Arbeitseinheiten.
Die erste Unit of Work soll testen, ob ein gültiger Benutzer bei gültigen und ungültigen Eingaben zurückgegeben wird oder nicht.
Alle Ausnahmen, die von der Datenquelle ausgelöst werden, müssen hier behandelt werden: Wenn kein Benutzer vorhanden ist, sollte es einen Test geben, der zeigt, dass eine Ausnahme ausgelöst wird, wenn der Benutzer nicht gefunden werden kann. Ein Beispiel hierfür könnte die IllegalArgumentException
sein, die mit dem Vermerk @Test(expected = IllegalArgumentException.class)
abgefangen wird.
Sobald Sie alle Anwendungsfälle für diese grundlegende Arbeitseinheit behandelt haben, gehen Sie eine Stufe höher. Hier machen Sie genau dasselbe, aber Sie behandeln nur die Ausnahmen, die von der Ebene direkt unter der aktuellen Ebene kommen. Auf diese Weise bleibt Ihr Testcode gut strukturiert und Sie können die Architektur schnell durchlaufen, um herauszufinden, wo etwas schief läuft, anstatt überall herumzuspringen.
An dieser Stelle sollte klar sein, wie wir mit diesen Ausnahmen umgehen werden. Es gibt 2 Arten von Eingaben: gültige Eingaben und fehlerhafte Eingaben (die Eingabe ist im strengen Sinne gültig, aber nicht korrekt).
Wenn Sie mit gültigen Eingaben arbeiten, setzen Sie die implizite Erwartung voraus, dass jeder Test, den Sie schreiben, funktionieren wird.
Ein solcher Methodenaufruf kann wie folgt aussehen: existingUserById_ShouldReturn_UserObject
. Wenn diese Methode fehlschlägt (z.B.: eine Exception wird ausgelöst), dann wissen Sie, dass etwas schief gelaufen ist und Sie können anfangen zu suchen.
Durch Hinzufügen eines weiteren Tests (nonExistingUserById_ShouldThrow_IllegalArgumentException
), der die fehlerhafte Eingabe verwendet und eine Ausnahme erwartet, können Sie sehen, ob Ihre Methode bei falscher Eingabe das tut, was sie tun soll.
Sie haben versucht, in Ihrem Test zwei Dinge zu tun: auf gültige und fehlerhafte Eingaben zu prüfen. Indem Sie dies in zwei Methoden aufteilen, die jeweils eine Sache tun, erhalten Sie viel klarere Tests und einen besseren Überblick darüber, wo etwas schief läuft.
Indem Sie die geschichtete Arbeitseinheit im Auge behalten, können Sie auch die Anzahl der Tests reduzieren, die Sie für eine Schicht benötigen, die in der Hierarchie höher liegt, weil Sie nicht alles berücksichtigen müssen, was in den unteren Schichten schief gelaufen sein könnte: Die Schichten unter der aktuellen sind eine virtuelle Garantie, dass Ihre Abhängigkeiten funktionieren, und wenn etwas schief läuft, dann in Ihrer aktuellen Schicht (vorausgesetzt, die unteren Schichten werfen selbst keine Fehler).
Wenn Sie das Pech haben, alle Fehler in Ihrem Code zu finden. Sie können dummerweise tun
class DumpTest {
Exception ex;
@Test
public void testWhatEver() {
try {
thisShouldThroughError();
} catch (Exception e) {
ex = e;
}
assertEquals(null,ex);
}
}
Wenn Sie testen möchten, ob Ihr Testziel die Ausnahme konsumiert. Lassen Sie den Test einfach als (mock collaborator mit jMock2):
@Test
public void consumesAndLogsExceptions() throws Exception {
context.checking(new Expectations() {
{
oneOf(collaborator).doSth();
will(throwException(new NullPointerException()));
}
});
target.doSth();
}
Der Test wäre erfolgreich, wenn Ihr Ziel die geworfene Ausnahme konsumiert, andernfalls würde der Test fehlschlagen.
Wenn Sie die Logik für die Verwendung von Ausnahmen testen wollen, werden die Dinge komplexer. Ich schlage vor, den Verbrauch an einen Kollaborateur zu delegieren, der gespottet werden kann. Der Test könnte also wie folgt aussehen:
@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();
}
Aber manchmal ist es überdimensioniert, wenn man es nur protokollieren will. In diesem Fall kann dieser Artikel (http://java.dzone.com/articles/monitoring-declarative-transac, http://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-there-an-easy-way/) helfen, wenn Sie in diesem Fall auf tdd bestehen.