Unit-Tests mit NetBeans

Schon viele Jahre hat das wohl bekannteste Unit-Test-Framework JUnit nun schon auf dem Buckel. Die Art wie wir damit heute Tests schreiben hat sich in dieser Zeit sehr stark verändert. Leider haben sich noch nicht alle IDE’s an diese Veränderung angepasst. Schauen wir uns mal an was NetBeans 8.0.1 für die Methode
public long addition(final int summand1, final int summand2)
für ein Codeskelett erzeugt:

   @Test
   public void testAddition() {
      System.out.println("addition");
      int sum1 = 0;
      int sum2 = 0;
      CalculatorImpl instance = new CalculatorImpl();
      long expResult = 0L;
      long result = instance.addition(sum1, sum2);
      assertEquals(expResult, result);
      // TODO review the generated test code and remove the default call to fail.
      fail("The test case is a prototype.");
   }

Mehr Code ist nicht immer besser! Und tatsächlich bin ich einmal mehr von diesem Codegenerat sehr enttäuscht. Analysieren wir es Stück für Stück und halten wir uns dabei vor Augen, dass wir auch für so eine einfache Methode mehrere Tests implementieren würden, um die verschiedenen Grenzbereiche abzutesten. Dieser Codeblock würde sich somit n-fach (mit unterschiedlichen Parametern und Soll-Resultat) wiederholen.

Als erstes entfernen wir natürlich den absichtlichen fail-Aufruf und den Kommentar, der blutige Anfänger davor bewahrt zu glauben, dieser Testfall sei ein fertiger Testfall.

   @Test
   public void testAddition() {
      System.out.println("addition");
      int sum1 = 0;
      int sum2 = 0;
      CalculatorImpl instance = new CalculatorImpl();
      long expResult = 0L;
      long result = instance.addition(sum1, sum2);
      assertEquals(expResult, result);
   }

Im nächsten Schritt entfernen wir das System.out.println("addition");. Denn dieses ist nicht nur völlig sinnlos, es schadet sogar! Denken wir daran, dass ein grösseres Projekt hunderte, wenn nicht tausende von einzelnen Testfällen enthalten kann. Da macht es absolut keinen Sinn so etwas (nichtssagendes) wie addition auszugeben. Denn diesen Output wird in der Regel niemand jemals lesen! Stattdessen wird es aber den Build verlangsamen und die Buildlogs unnötig aufblasen. Somit entfernen wir diese unnütze Ausgabe:

   @Test
   public void testAddition() {
      int sum1 = 0;
      int sum2 = 0;
      CalculatorImpl instance = new CalculatorImpl();
      long expResult = 0L;
      long result = instance.addition(sum1, sum2);
      assertEquals(expResult, result);
   }

Was testet dieser Testfall überhaupt? Um das zu verstehen müssen wir tatsächlich fünf bis sechs Codezeilen analysieren: Wir finden heraus, dass die beiden Summanden ‘0’ sind, und dass deren Addition somit auch ‘0’ ergeben soll. Aber nützen uns dafür die zwei lokalen Variablen sum1 und sum2 etwas? Nein. Also entfernen wir sie:

   @Test
   public void testAddition() {
      CalculatorImpl instance = new CalculatorImpl();
      long expResult = 0L;
      long result = instance.addition(0, 0);
      assertEquals(expResult, result);
   }

Jetzt ist sehr viel schneller klar, dass die beiden Summanden ‘0’ addiert werden! Das Gleiche können wir auch mit dem erwarteten Resultat machen. Die dafür (noch dazu an einer sehr merkwürdigen Position) deklarierte lokale Variable expResult ist absolut überflüssig. Viel deutlicher wird der Testfall, wenn wir den Soll-Wert direkt als Konstante einfügen:

   @Test
   public void testAddition() {
      CalculatorImpl instance = new CalculatorImpl();
      long result = instance.addition(0, 0);
      assertEquals(0L, result);
   }

Und was ist mit dem tatsächlichen Resultat? Brauchen wir dafür eine Variable? Oder wird der Code durch deren Entfernung nicht abermals leichter und schneller Verständlich? Sehen wir es uns an:

   @Test
   public void testAddition() {
      CalculatorImpl instance = new CalculatorImpl();
      assertEquals(0L, instance.addition(0, 0));
   }

Tatsächlich erfassen wir jetzt in der letzten Zeile mit einem einzigen Blick was getestet werden soll: 0 = 0 + 0. Die vielleicht etwas merkwürdige Reihenfolge ist der API von JUnit geschuldet, die als ersten Parameter den Soll- und als zweiten den Ist-Wert erwartet.
Als letzter Schritt ist die Versuchung jetzt gross, auch die letzte noch verbliebene lokale Variable zu eliminieren. Da wir nur eine einzige Methode aufrufen, müssen wir uns die Referenz auf den Testkandidaten tatsächlich nicht merken:

   @Test
   public void testAddition() {
      assertEquals(0L, new CalculatorImpl().addition(0, 0));
   }

Nun haben wir plötzlich einen Einzeiler! Auf einen einzigen Blick erfassen wir die Intention des Testfalles! Und im Nachgang stellen wir fest, dass das Schreiben dieses guten, einzeiligen Testfalles sogar viel schneller geht, als das von NetBeans erzeugte Codeskelett zu refaktorieren.

Ein Gedanke zu „Unit-Tests mit NetBeans

  1. Pingback: Verzeichnisstruktur von Java-Projekten | Roland Gisler

Leave a Reply

Your email address will not be published. Required fields are marked *