Advanced   Java   Services Parameterized Test


Tests mit verschiedenen Parametern wiederholen

Methoden haben meistens Eingangsparameter und liefern dazu Ergebnisse. Fehler treten oft nur für bestimmte Eingangsparameter auf, für andere dagegen nicht. Es ist deshalb sinnvoll einen Test mit verschiedenen Parametern zu wiederholen. Um Tests mit verschiedenen Parametern zu wiederholen müssen wir die generierte Testklasse von Hand ergänzen. Dazu sind mehrere Schritte notwendig.


Die zu testende Klasse

Wir gehen von einer sehr einfachen Klasse Datum aus. Sie enthält eine statische Methode, die wir testen wollen.

public class Datum
{
  public static boolean isLeapYear(int year)
  {
    return (year%4 ==0 && year%100 != 0) || year%400 == 0;
  }
}

Wir erzeugen mit JUNit eine Testklasse.

import org.junit.Test;

public class DatumTest
{
  @Test
  public void testIsLeapYear()
  {
  }
}

Schritt 1: Einfügen von @RunWith(Parameterized.class)

Diese Annotation muß vor der Testklasse stehen. Sie gibt JUnit bekannt, daß wir eine Methode mit einer gewissen Anzahl von Werten wiederholt ausführen lassen wollen.

@RunWith(Parameterized.class)

Die Annotation braucht zwei Importe, der zweite Import wird von Eclipse nicht entdeckt und muß von Hand eingefügt werden.

import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

Schritt 2: Füllen der Testmethode

Da wir nur eine Testmethode haben ist die Situation einfach. Wir wollen der Methode isLeapYear(int j) für verschiedene Jahreszahlen aufrufen und setzen eine zunächst noch undefinierte Variable ein.

@Test
public void testIsLeapYear()
{
  boolean boo = Datum.isLeapYear(year);
  Assert.assertTrue(year + " ist ein Schaltjahr : " + boo, boo);
}

Schritt 3: Ergänzen eines Konstruktors

Die undefinierte Variable wird jetzt im Datenteil der Testklasse vereinbart. Sie erhält ihren Wert über einen passenden Konstruktor der Testklasse. Beim Instanziieren des Testobjekts kann damit die Variable initialisiert werden. JUnit legt ja für jede zu testende Methode ein eigenes Objekt an. Auf diese Weise gelangt der Parameter in die Testmethode.

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Parameterized.class)
public class DatumTest
{
  private int year;

  public DatumTest(int year)
  {
    this.year = year;
  }

  @Test
  public void testIsLeapYear()
  {
    boolean boo = Datum.isLeapYear(year);
    Assert.assertTrue(year + "ist ein Schaltjahr", boo);
  }
}

Nun legen wir noch eine statische Methode an. Mit ihr wird der Konstruktor der Testklasse mit konkreten Daten versorgt.


Schritt 4: Ergänzen einer statischen Methode

Der Name der statischen Methode kann theoretisch frei gewählt werden, man folgt aber der Konvention. Der Returntyp ist festgelegt und muß vom Typ Collection<Object[]> sein. Die Methode muß zudem die Annotation @Parameters erhalten, außerdem darf sie nicht null zurückgeben.

Für unseren Fall kann die Methode z. Bsp. so aussehen:

@Parameters
public static Collection<Object[]> getParams()
{
   Object[][] data = new Object[][] { {1800} , {1900}, {2000} , {2014}, {2015} , {2016} };
   return Arrays.asList(data);
}

Hier ist wichtig: data muß ein zweidimensionales Array vom Typ Object sein, auch wenn man in diesem Fall ein eindimesionales Arrays verwenden könnte!


Die fertige Testklasse.
import java.util.Arrays;
import java.util.Collection;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class DatumTest
{
  private int year;

  public DatumTest(int year)
  {
    this.year = year;
  }

  @Test
  public void testIsLeapYear()
  {
    boolean boo = Datum.isLeapYear(year);
    Assert.assertTrue(year + "ist ein Schaltjahr", boo);
  }


  @Parameters
  public static Collection<Object[]> getParams()
  {
     Object[][] data = new Object[][] { {1600}, {1800}, {2000} , {2010}, {2015} , {2016} };
     return Arrays.asList(data);
  }
}

Schritt : Starten der Testfolge

Die Testfolge wird wie ein einzelner Test gestartet. JUnit erkennt an Hand der Annotations, daß der Test mit den durch die statische Methode gelieferten Daten wiederholt werden soll. In diesem Fall gibt es also sechs Durchläufe.

eclipse-junit-25.jpg


Einschränkung

Es ist nicht möglich mehrere Konstruktoren zu schreiben und die übergebenen Daten verschiedenen Testmethoden zuzuordnen. Will man mehrere Testmethoden wiederholen muß man alle Parameter über einen Konstruktor übergeben.


Mehrere Testmethoden wiederholen

Wie wollen die zwei Methoden de folgenden Klasse testen:

public class Datum
{
  private int tag, monat, jahr;

  public static boolean isLeapYear(int year)
  {
    return (year%4 == 0 && year%100 != 0) || year%400 == 0;
  }

  public Datum(int tag, int monat, int jahr)
  {
    this.tag = tag;
    this.monat = monat;
    this.jahr = jahr;
  }

  /**
   *  Liefert den Wochentag als String
   */
   public String getWochentag()
  {
    int wochentag;
    int h = monat, k = jahr ;

      if (monat < 3)
      {
         h = monat + 12 ;
         k = jahr-1 ;
      }

      //wochentag = (tag+2*h + (3*h+3)/5 + k + k/4 - k/100 + k/400 + 1)%7 ; // richtige Formel
      // Fehler in der Formel
      wochentag = (tag+2*h + (3*h+3)/5 + k + k/4 + k/100 - k/400 + 1)%7 ;

      switch (wochentag)
      {
         case 0 : return "Sonntag";
         case 1 : return "Montag";
         case 2 : return "Dienstag";
         case 3 : return "Mittwoch";
         case 4 : return "Donnerstag";
         case 5 : return "Freitag";
         case 6 : return "Samstag";
         default: return "gibt's nicht";
      }
  }
}  // end class

Dazu erzeugt uns JUnit folgende Testklasse.

import static org.junit.Assert.*;
import org.junit.Test;

public class DatumTest
{
  @Test
  public void testIsLeapYear()
  {
    fail("Not yet implemented"); // TODO
  }

  @Test
  public void testGetWochentag()
  {
    fail("Not yet implemented"); // TODO
  }
}

Die wir wie folgt ergänzen

import org.junit.Test;
import org.junit.Assert;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Parameterized.class)
public class DatumTest
{
  private int year;
  private int tag, monat, jahr;
  private String wochentag;

  @Test
  public void testIsLeapYear()
  {
    boolean boo = Datum.isLeapYear(year);
    Assert.assertTrue(year + " ist ein Schaltjahr: " + boo, boo);
  }

  @Test
  public void testGetWochentag()
  {
    Datum date = new Datum(tag, monat, jahr);
    String wtag = date.getWochentag();
    Assert.assertEquals(wochentag, wtag);
  }
}

Jetzt brauchen wir noch einen Konstruktor und die statische Methode getParams()

import org.junit.Test;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Assert;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class DatumTest
{
  private int year;
  private int tag, monat, jahr;
  private String wochentag;


  public DatumTest(int tag, int monat, int jahr, String wochentag, int year)
  {
    this.tag = tag;
    this.monat = monat;
    this.jahr = jahr;
    this.wochentag = wochentag;
    this.year = year;
  }

  @Parameters
  public static Collection<Object[]> getParams()
  {
     Object[][] data = new Object[][] { { 1, 1, 2016, "Freitag",  1600},
                                        { 1, 2, 2016, "Montag",   1800},
                                        { 1, 3, 2016, "Dienstag", 2000} ,
                                        { 1, 4, 2016, "Freitag",  2010},
                                        { 1, 5, 2016, "Sonntag",  2016}  };
     return Arrays.asList(data);
  }


   @Test
   public void testIsLeapYear()
   {
      boolean boo = Datum.isLeapYear(year);
      Assert.assertTrue(year + " ist ein Schaltjahr: " + boo, boo);
   }

   @Test
   public void testGetWochentag()
   {
      Datum date = new Datum(tag, monat, jahr);
      String wtag = date.getWochentag();
      Assert.assertEquals(wochentag, wtag);
   }
}

Der Testlauf

eclipse-junit-26.jpg


Wiederholen ohne Parameterübergabe

Durch geschickte Wahl der statischen Methode kann man beliebige Wiederholungen ohne Parameterübergabe erreichen. Man kann manchmal auch die Daten in der Testklasse direkt erzeugen. Mit der folgenden Testklasse kann man z. Bsp. die Methode isLeapYear(year) ebenfalls testen.

import org.junit.Test;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Assert;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class DatumTest
{
  private static int year = 2009;

  public DatumTest()
  {
    ++year;
  }

  @Parameters
  public static Collection<Object[]> generateParams()
  {
      return Arrays.asList(new Object[7][0]);
  }

   @Test
   public void testIsLeapYear()
   {
      boolean boo = Datum.isLeapYear(year);
      Assert.assertTrue(year + " ist ein Schaltjahr: " + boo, boo);
   }
}

Der Trick ist hier, die Variable statisch anzulegen. Da JUnit für jeden Durchlauf ein neues Objekt anlegt kann man nur so die Variable hochzählen.

eclipse-junit-27.jpg