Close to 100%?

13.08.2008 Permalink

Uncle Bob: "True professionals write unit tests that cover close to 100% of their code."

Uh, so sehr ich schätze, was Mr. Martin uns über Design zu sagen hat, das hier über Unittests geht zu weit.

Es ist sicher wünschenswert, 100% an Zeilenabdeckung zu schaffen, es ist aber a) wirtschaftlich nicht sinnvoll und b) ein in der Projektpraxis schädliches Ziel.

100% Zeilenabdeckung heißt nämlich auch: alle Getter und Setter. Und es heißt: jeden Block zur Ausnahmebehandlung. Je nach Lesart heißt es sogar: generierter Code. Wie jede Maßnahme zur Sicherung von Qualität muss der Aufwand für den Aufbau und die Pflege von Unittests kleiner sein als der zu erwartende Aufwand durch spätes Finden und Fixen. Dass triviale Einzeiler zu diesem Aufwand etwas beitragen, ist sicher Unfug. Also ist 100% Abdeckung wirtschaftlich nicht zu rechtfertigen.

Aber welche Zahl ist dann besser?
50% oder 80%? Beides nicht richtig, denn schon die Frage ist falsch. Das Problem ist, dass eine simple Zielvorgabe verwischt, worauf es beim Unittest ankommt: zu verhindern, dass sich unbemerkt Fehler in den Code einschleichen, die in späteren Teststufen entweder teuer gefunden und behoben oder aber -- noch schlimmer -- in den Betrieb wandern und irgendwann noch teurere Ausfälle verursachen.

Menschen richten ihre Arbeit nach dem aus, woran sie gemessen wird. Wer 100% will, kriegt 100%. Notfalls mit roher Gewalt, z.B. durch auf Reflection basierende, generische Methodenaufrufer, die sinnlos die Zeilenabdeckung hochtreiben. Das ist keine Erfindung von mir, das ist eine Beobachtung aus der Vergangenheit. Das Ziel 100% (oder 50%) ist deshalb schädlich, weil es nicht angibt, wo wir wirklich hinwollen.

Wie denn dann?
Es kommt beim Test immer darauf an, viele Fehler mit wenig Aufwand zu finden. Viele Fehler findet man dort, wo viele gemacht werden. Viele werden dort gemacht, wo der Code komplex oder unübersichtlich ist. Ein (und nur ein!) Vertreter sind Methoden ab mittlerer zyklomatischer Komplexität (CCN > 5) oder schlicht sehr vielen Befehlen (NCSS > 10). Aber es gehören beispielsweise auch simple Finder-Methoden der Form

    public List<Modul> findByAenderungsanforderung(String schluesselCode) {
        Query q = em.createQuery("select m from Modul m, Modulaenderung ma
				    where m = ma.modul and ma.aenderungsanforderung.code = ?1");
        q.setParameter(1,schluesselCode);
        return q.getResultList();
    }
dazu. Nicht, weil der Javacode komplex ist, sondern weil in JPA-QL Statements eine Menge schief gehen kann. Ich kann 100% Zeilenabdeckung haben und trotzdem keine Idee haben, ob meine Queries in nicht-trivialen Datenkonstellationen funktionieren.

Wer nach einer einfachen Zielaussage sucht, die uns etwas näher an unser wirkliches Ziel bringt, kann z.B. ausgeben: alle Methoden mit CCN > 5 und alle Finder in DAOs müssen abdeckt sein. Das ist noch nicht gut, weil eben meine Testdaten auch eine Rolle spielen, aber schon etwas besser als die blöden x Prozent.

Kann man das noch messen?
Der Charme der einfachen 100% (oder 50%) Zielvorgabe ist, dass ich die passenden Werte direkt aus Code-Coverage-Tools herausbekomme. Aber der Hammer in der Hand und der Schraubendreher auf dem Fußboden ist noch kein guter Grund, Schraube und Wand zu ruinieren, weil ich zu faul bin mich zu bücken. Ein wenig Filtern und im Anschluss Rechnen hilft da ungemein. Also z.B.: betrachte nur relevante Methoden, d.h. deren CCN > 5 ist oder die "find..." heißen und in DAO-Implementierungen stecken. Zähle davon die Methoden, die 0% Zeilenabdeckung haben und setze das ins Verhältnis zur Anzahl aller relevanten Methoden. Je kleiner der Quotient ist, desto besser. Auf Basis dieser Metrik ist man schon ein wenig näher dran...

Geht's noch einfacher?
Am einfachsten wäre es, wenn die Mitarbeiter lernen, dass Unittests an den richtigen Stellen eigene Zeit und Nerven sparen. Dass solche Unittests sich sehr schnell amortisieren und damit unterm Strich umsonst sind. Dann benützt man die Zeilenabdeckungswerte lediglich, um größere übersehene Stellen aufzudecken. Für Entwickler wird das Code-Coverage-Tool außerdem eine enorm nützliche Hilfe, denn die Ausgaben von Werkzeugen wie z.B. EMMA oder Cobertura, mit denen man durch den Code browsen kann, zeigen anschaulich die roten, also unabgedeckten Flecken. Simple Zielvorgaben dagegen helfen nur dem schlechten Gewissen schlecht beratener Entwicklungsleiter, die ihren Mitarbeitern nicht sagen können, worauf es ankommt. Solche Zielvorgaben erweisen der Organisation, die die Software erstellt und über Jahre warten muss, einen Bärendienst, da sie nicht aussagen, was man eigentlich möchte. Stattdessen lenken sie vom wesentlichen Ziel ab: effizientere Softwareentwicklung.

Praktische Hinweise
Um ein Werkzeug wie EMMA oder Cobertura an den Start zu bringen, sind ein bis zwei Stunden erforderlich. Man sollte die ersten Ergebnisse sehr genau mit der eigenen Erwartung vergleichen, da Fehler beim Messen extrem schnell passieren und nicht leicht zu finden sind.

Um die Methoden zu filtern, bei denen sich Unittests wirklich lohnen, benötigt man wenigstens die Berechnung der zyklomatischen Komplexität (CCN) nach McCabe. JavaNCSS ist dazu in wenigen Minuten zu überreden. Bei Cobertura wird die CCN sogar gleich mitberechnet.

Wer verschiedene Metriken aus verschiedenen Werkzeugen kombinieren will, um tiefere Einsichten zu erhalten und sich besser auf die wichtigen Stellen zu konzentrieren, kann entweder mittels XSLT die XML-Ausgaben der verschiedenen Werkzeuge in ein gemeinsames Format (z.B. CSV) bringen und dann mit der Lieblingstabellenkalkulation gruppieren, sortieren und filtern oder direkt XRadar ausprobieren.

Als Ablaufsteuerung für die verschiedenen Schritte bietet sich allgemein Ant an, für das viele Werkzeuge direkt eigene Tasks mitliefern.

Wer auf die Bastelarbeit verzichten muss oder will und etwas Geld in die Hand nehmen kann, wird auf dem Markt sicher auch schön integrierte Lösungen finden. Wie bei jeder Toolauswahl kommt es aber auf die Ziele an. Hier würde ich großen Wert auf den Zugang zu den rohen Messdaten legen, da ich heute nicht entscheiden kann, was ich morgen fragen will.

Unittests und statische Codeanalysen sind zwei von drei praktikablen entwicklungsbegleitenden QS-Maßnahmen. Peer Reviews sind der dritte Pfeiler und im übrigen das preiswerteste und wirksamste Mittel, um früh Defekte aufzudecken.