Warum Unit-Tests so wichtig sind

Im letzten Artikel habe ich darüber philosophiert, warum ich Softwareentwickler geworden bin.

Heute philosophiere ich darüber, warum Qualität gerade in unserem Beruf so unglaublich wichtig ist.

Was ist Qualität überhaupt? Was meinen wir damit?

Software die funktioniert? Selbstverständlich.
Software die wartbar ist? unbedingt!
Software die einfach zu verstehen ist? Auf jedenfall!

Wie aber bekommen wir Qualität in unsere Softwaresysteme, oder besser gesagt, wie nicht?

Es gibt einen schöne Urban-Legend von einem Entwickler aus einem Projekt, in dem ich gearbeitet habe. Dieser Entwickler (wohlgemerkt Senior-Entwickler mit Erfahrung) hat sich strikt geweigert, Unit-Tests für seinen Code zu schreiben. Begründung: Mein Code ist auch ohne Tests gut genug.

Setzen, 6, Thema verfehlt und Konzept nicht verstanden.

Warum schreiben wir Unit-Tests und warum ist es so wichtig? Nicht um sicherzustellen, dass der Code funktioniert, das bekommen die meisten Entwickler (ja die meisten, nicht alle) auch ohne Unit-Tests hin.

Wir schreiben Unit-Tests um sicherzustellen, dass neue Änderungen im System nicht die bereits vorhandene Funktionalität kaputt machen. Unit-Tests sind unser Sicherheitsnetz, der doppelte Boden, der Fallschirm wenns Ernst wird.

Ein Beispiel:

Kürzlich habe ich ein ziemlich umfangreiches Refactoring an einer Serviceklasse durchgeführt. Diese Serviceklasse abstrahiert das Laden verschiedener Kundentypen von verschiedenen Backends und bietet einen einheitlichen View auf Kundendaten für die Applikation.

In dieser Klasse war haufenweise Code der folgenden Art zu finden (in Dummy-Code):

[code language=”java”]
public Kunde ladeKunde(KundenId id){
if(kundentyp ist KundenTyp A){
Lade Kunde von Backend A und mappe in das einheitliche User-Modell
} else if(kundentyp ist KundenTyp B){

}
}

public Status schreibeKunde(KundenId id){
if(kundentyp ist KundenTyp A){
Mappe in das Backendmodell und schreibe Kundendaten
} else if(kundentyp ist KundenTyp B){

}
}
[/code]

Diese Klasse war zwar hässlich und schwer zu verstehen, ABER komplett Unit getestet. Ohne Unit-Tests hätte sich weder ich noch ein anderer an diese Klasse rangetraut. Das Interface dieses Services war und ist sehr einfach (in Dummy-Code).

[code language=”java”]
public interface Kundenservice {
public Kunde ladeKunde(KundenId kunde) throws KundenDatenException
public Status speichereKunde(Kunde kunde) throws KundenDatenException
}
[/code]

Alle Unit-Tests haben sich an die wichtige Konvention gehalten, dass sie keine Internas der Implementierung testen, sondern nur gegen das Interface, also im Prinzip nur gegen die beiden Methoden.
Ein Unit-Test für diesen Service sah ungefähr so aus (in Dummy-Code mit Mockito-Mocks)

[code language=”java”]

@Mock
private CustomerServiceKundenTypeA typAService;
@Mock
private CustomerServiceKundenTypB typBService;
@InjectMocks
private KundenService testee;

@Before
public void setUp(){
testee = new KundenServiceImpl();
//injeziere Mockito-Mocks in testee
}

@Test
public void ladeKundenTypA(){
KundenId kundenId = KundenId.alsTypA(4711);
Kunden kunde = testee.ladeKunde(kundenId);
//verifiziere dass CustomerServiceKundenTypeA ladeKunde aufgerufen wurde
//verifiziere geladene Kundendaten
}
[/code]

Diese Tests gab es für jeden Kundentypen, für jede mögliche Konstellation und sogar Fehlerfälle, immer gegen das Interface getestet. Ein Traum.

Was war zu tun?

Ich habe die komplette Logik aus dem Kundenservice extrahiert und eigene CustomerTypeHandler implementiert.

[code language=”java”]
public interface CustomerTypeHandler {
public Kunde ladeKunde(KundenId kundenId);
public Status speichereKunde(Kunde kunde);
public boolean accepts(KundenId);
}
[/code]

Fast lle if-else-Konstrukte aus dem Kundenservice konnten direkt gelöscht werden, der Kundenservice dient fortan nur noch als Facade für die CustomerTypeHandler und kümmert sich nur noch darum, den korrekten Handler zu finden. Beispiel:

[code language=”java”]
public Kunde ladeKunde(KundenId kundenId){
for(CustomerTypeHandler handler : handlers){
if(handler.accepts(kundenId)){
return handler.ladeKunde(kundenId);
}
}
}
[/code]

Das schöne ist, dass sich an der Schnittstelle des Kundenservice nichts geändert hat, es handelt sich also beinahe um ein Bilderbuch-Refactoring. Wir ändern die interne Funktionsweise, ohne die Schnittstelle anzufassen.
Nach dem Refactoring waren alle Tests, die vorher grün waren auch weiterhin grün.
Es war also nicht einmal nötig, das Portal hochzufahren um sicherzustellen, dass alles noch wie zuvor funktioniert.

Im Nachgang wurden dann die Tests auf die einzelnen CustomerTypeHandler verteilt, und der Kundenservice selbst testet nur noch, dass der korrekte TypeHandler getriggert wird.

Ohne Unit-Tests wäre diese Refactoring gefährlich bis sogar unmöglich gewesen, ohne jeden möglichen Fall manuell nachzutesten.

Leider mache ich nach wie vor bei vielen Entwicklern die Erfahrung, dass die Wichtigkeit von Unit-Tests nicht verstanden wird. Und jedesmal wenn ich frage, ob für neue Funktionalität Unit-Tests geschrieben wurden sehe ich rollende Augen und genervte Blicke.

Software ohne Unit-Tests ist unprofessionell, dann könnte auch ein Automobilhersteller Autos ohne Crash-Tests ausliefern. Kann funktionieren, ist aber reine Glückssache. Und daran gibt es nichts zu rütteln.


War dieser Blogeintrag für Sie interessant? Evtl. kann ich noch mehr für Sie tun.

Trainings & Know-How aus der Praxis zu

  • Apache Wicket 1.4.x, 1.5.x, 1.6.x
  • GIT – Best Practices, Einsatz, Methoden
  • Spring
  • Java
  • Scrum & Kanban
  • Agiles Arbeiten
Consulting & Softwareentwicklung

  • Requirements Engineering
  • Qualitätssicherung
  • Software-Entwicklung
  • Architektur
  • Scrum & Kanban

Effective Trainings & Consulting - Martin Dilger



Hat Ihnen dieser Blog-Eintrag gefallen? Ich stelle in diesem Blog Informationen über Tools, Frameworks und Werkzeuge zur Verfügung, die mich produktiver machen. Vielleicht kann ich auch Ihnen helfen, produktiver zu werden.


Ich unterstütze Sie als freier Mitarbeiter bei der Entwicklung von Software-Projekten, Agiler Arbeit sowie Schulungen / Fortbildungen.


Jeden Tag ein bisschen produktiver - ab heute