In der realen Welt ist es völlig normal, Komponententests für den Code eines anderen zu schreiben. Sicher, der ursprüngliche Entwickler hätte dies bereits tun sollen, aber häufig erhalten Sie einen Legacy-Code, in dem dies einfach nicht getan wurde. Übrigens spielt es keine Rolle, ob dieser Legacy-Code vor Jahrzehnten aus einer weit entfernten Galaxie stammt oder ob einer Ihrer Mitarbeiter ihn letzte Woche eingecheckt hat oder ob Sie ihn heute geschrieben haben. Legacy-Code ist Code ohne Tests
Fragen Sie sich: Warum schreiben wir Komponententests? Going Green ist offensichtlich nur ein Mittel zum Zweck. Das ultimative Ziel ist es, Aussagen über den getesteten Code zu beweisen oder zu widerlegen.
Angenommen, Sie haben eine Methode, die die Quadratwurzel einer Gleitkommazahl berechnet. In Java würde die Schnittstelle Folgendes definieren:
public double squareRoot(double number);
Es spielt keine Rolle, ob Sie die Implementierung geschrieben haben oder ob es jemand anderes getan hat, Sie möchten ein paar Eigenschaften von squareRoot behaupten:
- dass es einfache Wurzeln wie sqrt (4.0) zurückgeben kann
- dass es eine echte Wurzel wie sqrt (2.0) mit einer vernünftigen Genauigkeit finden kann
- dass es herausfindet, dass sqrt (0.0) 0.0 ist
- dass es eine IllegalArgumentException auslöst, wenn eine negative Zahl eingegeben wird, dh auf sqrt (-1.0)
Sie schreiben diese also als Einzeltests:
@Test
public void canFindSimpleRoot() {
assertEquals(2, squareRoot(4), epsilon);
}
Hoppla, dieser Test schlägt bereits fehl:
java.lang.AssertionError: Use assertEquals(expected, actual, delta) to compare floating-point numbers
Sie haben die Gleitkomma-Arithmetik vergessen. OK, du stellst vor double epsilon=0.01
und gehst:
@Test
public void canFindSimpleRootToEpsilonPrecision() {
assertEquals(2, squareRoot(4), epsilon);
}
und füge die anderen Tests hinzu: endlich
@Test
@ExpectedException(IllegalArgumentException.class)
public void throwsExceptionOnNegativeInput() {
assertEquals(-1, squareRoot(-1), epsilon);
}
und hoppla nochmal:
java.lang.AssertionError: expected:<-1.0> but was:<NaN>
Sie sollten getestet haben:
@Test
public void returnsNaNOnNegativeInput() {
assertEquals(Double.NaN, squareRoot(-1), epsilon);
}
Was haben wir hier gemacht? Wir haben mit ein paar Annahmen über das Verhalten der Methode begonnen und festgestellt, dass nicht alle zutreffen. Wir haben dann die Testsuite Green erstellt, um den Nachweis zu erbringen, dass sich die Methode gemäß unseren korrigierten Annahmen verhält. Jetzt können sich Clients dieses Codes auf dieses Verhalten verlassen. Wenn jemand die tatsächliche Implementierung von squareRoot durch etwas anderes austauschen würde, was beispielsweise wirklich eine Ausnahme auslöste, anstatt NaN zurückzugeben, würden unsere Tests dies sofort feststellen.
Dieses Beispiel ist trivial, aber häufig erben Sie große Codeteile, bei denen nicht klar ist, was sie tatsächlich tun. In diesem Fall ist es normal, einen Testgurt um den Code zu legen. Beginnen Sie mit ein paar grundlegenden Annahmen darüber, wie sich der Code verhalten soll, schreiben Sie Unit-Tests für sie, testen Sie. Wenn Grün, gut, schreibe mehr Tests. Wenn Rot, haben Sie jetzt eine fehlgeschlagene Behauptung, die Sie gegen eine Spezifikation halten können. Vielleicht liegt ein Fehler im Legacy-Code vor. Vielleicht ist die Spezifikation über diesen bestimmten Eingang unklar. Vielleicht haben Sie keine Spezifikation. In diesem Fall schreiben Sie den Test so, dass das unerwartete Verhalten dokumentiert wird:
@Test
public void throwsNoExceptionOnNegativeInput() {
assertNotNull(squareRoot(-1)); // Shouldn't this fail?
}
Mit der Zeit erhalten Sie ein Testkabel, das das tatsächliche Verhalten des Codes dokumentiert und zu einer Art codierter Spezifikation wird. Wenn Sie den alten Code jemals ändern oder durch einen anderen Code ersetzen möchten, können Sie mithilfe des Testkabels überprüfen, ob sich der neue Code gleich oder anders verhält als erwartet und kontrolliert (z. B. tatsächlich) behebt den Fehler, von dem Sie erwarten, dass er behoben wird). Dieses Gurtzeug muss am ersten Tag nicht komplett sein. Tatsächlich ist es fast immer besser, ein unvollständiges Gurtzeug zu haben, als überhaupt kein Gurtzeug zu haben. Ein Gurtzeug zu haben bedeutet, dass Sie Ihren Client-Code einfacher schreiben können, dass Sie wissen, wo die Dinge brechen, wenn Sie etwas ändern, und wo sie kaputt sind, als sie es schließlich taten.
Sie sollten versuchen, aus der Einstellung herauszukommen, dass Sie Komponententests schreiben müssen, nur weil Sie müssen, als würden Sie Pflichtfelder in einem Formular ausfüllen. Und Sie sollten keine Unit-Tests schreiben, nur um die rote Linie grün zu machen. Unit-Tests sind nicht deine Feinde, Unit-Tests sind deine Freunde.