JUnit – Variante: Parameterized

Ich habe mich wieder einmal an der „Lines of Code“ Kata versucht und dabei bereits nach drei simplen Tests festgestellt, eigentlich ist es ja immer das Gleiche nur mit anderen Werten.
In u.a. Klasse zeige ich den Test zum aktuellen Zeitpunkt vor dem Refactoring:

package loc;

import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;

public class LinesOfCodeCounterTest {

    @Test
    public void testSimpleCodeLines() {
        String code2Test = "public void doIt() {\n}";
        LinesOfCodeCounter counter = new LinesOfCodeCounter();
        assertThat(counter.count(code2Test), is(2));
    }

    @Test
    public void testSimpleCodeLinesWithLineComment() throws Exception {
        String code2Test = "// Kommentar\n"
                          +"public void doIt() {\n"
                          +"System.out.println(\"Hello world!\");\n"
                          +"}";
        LinesOfCodeCounter counter = new LinesOfCodeCounter();
        assertThat(counter.count(code2Test), is(3));        
    }
    
    @Test
    public void testCodeLinesWithBlockComment() throws Exception {
        String code2Test = "/* block comment \n"
                          +" second line \n"
                          +"*/\n"
                          +"public void ...";
        LinesOfCodeCounter counter = new LinesOfCodeCounter();
        assertThat(counter.count(code2Test), is(1));
    }
}

Was soll man in solch einem Fall tun? Ganz einfach – mit parametrisierten Tests arbeiten.

Im Endeffekt brauche ich einen String, der den Quellcode enthält (java.lang.String) und die zu prüfende Anzahl an Codezeilen (int).

Beide Testdatenwerte lege ich mir im ersten Schritt als Attribute/Felder meiner Testklasse an.
Zudem implementiere ich einen Konstruktor für die Testklasse, der (wenigstens) die beiden Parameter übergeben kriegt.

private String code2Test;
private int numberOfCodeLines;

public LinesOfCodeCounterTest(String code, int numberOfLines [,...]) {
    code2Test = code;
    numberOfCodeLines = numberOfLines;
 ...
}

Dann verwende ich in meiner Testmethode eben genau diese Parameter als Eingabedaten.

@Test
public void testSimpleCodeLines() {
   assertThat(counter.count(code2Test), is(numberOfCodeLines));
}

Nun muss ich noch sicherstellen, dass meine Parameter erstens zur Verfügung stehen und zweitens auch darüber iteriert wird:

@Parameters
public static Iterable<Object[]> data() {
   return Arrays.asList(new Object[][] {
      { "...", 2 },
      { "...", 3 },
      ...});
}

Es wird also ein statischer Iterator über einem zweidimensionalen Array deklariert und mit den entsprechenden Testdaten zusammengebaut.
Der Basistyp Object hat Sinn, denn wir wissen nicht immer, welche Testdatentypen wir unterstützen müssen.
Zweidimensional ist das Array, weil es ein viele Testdatenreihen sind:

  1. Testdatenreihe
    "public void doIt() {\n}" --> 2 (Code Zeilen)
  2. Testdatenreihe
    "// Kommentar\npublic void doIt() {\nSystem.out.println(\"Hello world!\");\n}" --> 3 (Code Zeile)
  3. Testdatenreihe
    "/* block comment \n second line \n*/\npublic void ..." --> 1 (Code Zeile)

Die zweite Dimension entsteht dadurch, dass wir mehrere Testdaten benötigen. In unserem Fall:

  1. SourceCode (dessen Codezeilen gezählt werden)
    "public void doIt() {\n}"
  2. Anzahl der Codezeilen, die erwartet werden.
    2

Über dieses Array wird nun automatisch iteriert, weil es mit der Annotation

@Parameters

versehen wurde.
Alles zusammengebaut sieht der Testcode wie folgt aus:

package loc;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.util.Arrays;

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 LinesOfCodeCounterTest {
private LinesOfCodeCounter counter;
private String code2Test;
private int numberOfCodeLines;

public LinesOfCodeCounterTest(String code, int numberOfLines,
                              String comment) {
   code2Test = code;
   numberOfCodeLines = numberOfLines;
   counter = new LinesOfCodeCounter();
}

	@Parameters(name = "Test case {index} - {2}")
	public static Iterable<Object[]> data1() {
	   return Arrays.asList(new Object[][] {
		  { "public void doIt() {\n}", 2, "No comments" },
		  { "// Kommentar\npublic void doIt() {\nSystem.out.println(\"Hello world!\");\n}", 3, "Line comment" },
		  { "/* block comment \n second line \n*/\npublic void ...", 1, "Block comment" }
		  });
	}

	@Test
	public void testSimpleCodeLines() {
		  assertThat(counter.count(code2Test), is(numberOfCodeLines));
	}
}

Was dabei nun auffällt ist in Zeile 20 ein zusätzlicher Parameter für den Konstruktor (comment), der durch die 3.Spalte des Parameters-Array mit einem String befüllt wird.
Dieser String wird vom JUnit Runner in Eclipse benutzt, um die Auflistung der Durchführung dieser Tests etwas sprechender zu gestalten.

Paramterized test in Eclipse

Wie man sieht wird statt [0], [1], [2] eben [Test case 0 – No comments], … ausgegeben.

Alternative Variante: JUnitParams

Es gibt hier eine pragmatische Alternative, die allerdings eine weitere Abhängigkeit zur Folge hat:

<dependency>
	<groupId>pl.pragmatists</groupId>
	<artifactId>JUnitParams</artifactId>
	<version>1.0.5</version>
	<scope>test</scope>
</dependency>

Danach ist der Einsatz von Parametern deutlich leichter, wie man an dem analogen Beispiel sieht:

package loc;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.util.Arrays;

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

public class LinesOfCodeCounterTestWithJUnitParams {
private LinesOfCodeCounter counter;
private String code2Test;
private int numberOfCodeLines;

@RunWith(JUnitParamsRunner.class)
public LinesOfCodeCounterTest {
	 @Test
	 @Parameters(
		 { "public void doIt() {\n}", 2, "No comments" },
		 { "// Kommentar\npublic void doIt() {\nSystem.out.println(\"Hello world!\");\n}", 3, "Line comment" },
		 { "/* block comment \n second line \n*/\npublic void ...", 1, "Block comment" }
		 )
	 public void testSimpleCodeLines(String code2Test, int numberOfLines, String failureText) {
	   assertThat(failureText, counter.count(code2Test), is(numberOfLines));
	 }
}
Parametrisierte Tests
Markiert in:        

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.