Jedną z najbardziej rozpoznawalnych cech BDDW jest opisowość testów. Oczywiście nie jest to najważniejsza cecha. Raczej jest to produkt uboczny metodyki. Jednak gdy piszemy testy, to taka opisowość jest bardzo przydatna. Zresztą widać, że narzędzia pozwalające na opisywanie testów w bardziej naturalny sposób zyskują popularność w ostatnich latach.

Jedną z rzeczy, których brakuje w JUnit 4, jest możliwość zdefiniowania opisu dla testu. Niestety zaszycie opisu w nazwie metody jest słabe. Jesteśmy ograniczeni przez język, a dodatkowo ciężko czyta się camel case. O notacji z podkreśleniami nie wspominam, bo oznacza to dodatkową zabawę z checkstyle. TestNG pozwalało nazywać testy, za pośrednictwem pola testName w adnotacji @Test:

Listing 1. Nazywanie testów w TestNG

@Test(suiteName = "FizzBuzz should")
public class FizzBuzzTestNGNamedTest {

	private FizzBuzz sut;

	@BeforeTest
	public void setup() {
		sut = new FizzBuzz();
	}

	@Test(testName = "return FizzBuzz when dividable by 3 and 5")
	public void shouldReturnFizzBuzzIfDiv3And5() throws Exception {
		assertEquals("FizzBuzz", sut.fizzBuzz(15));
	}

	@Test(testName = "return Buzz when dividable by 5")
	public void shouldReturnBuzzIfDiv5() throws Exception {
		assertEquals("Buzz", sut.fizzBuzz(5));
	}

	@Test(testName = "return Fizz when dividable by 3")
	public void shouldReturnFizzIfDiv3() throws Exception {
		assertEquals("Fizz", sut.fizzBuzz(3));
	}

	@Test(testName = "return number in other cases")
	public void shouldReturnVal() throws Exception {
		assertEquals("2", sut.fizzBuzz(2));
	}
}

Mechanizm ten nie jest jednak kompletny. Na przykład IntelliJ wyświetla nazwy metod, a nie testów. JUnit 5 wprowadza dwa inne mechanizmy.

Testy nazwane

Najprostszym mechanizmem jest nazwanie testów. Służy do tego specjalna adnotacja @DisplayName. Znowuż, nazwa adnotacji świetnie oddaje intencje. Wyświetlamy test pod pewną nazwą. Jego rzeczywista nawa, to nazwa metody 🙂

Listing 2. Użycie @DisplayName

@DisplayName("FizzBuzz should")
public class FizzBuzzJUnit5DescriptiveTest {

	private FizzBuzz sut;

	@BeforeEach
	public void setup() {
		sut = new FizzBuzz();
	}

	@Test
	@DisplayName("return FizzBuzz when dividable by 3 and 5")
	public void shouldReturnFizzBuzzIfDiv3And5() throws Exception {
		assertEquals("FizzBuzz", sut.fizzBuzz(15));
		assertEquals("FizzBuzz", sut.fizzBuzz(30));
		assertEquals("FizzBuzz", sut.fizzBuzz(150));
	}

	@Test
	@DisplayName("return Buzz when dividable by 5")
	public void shouldReturnBuzzIfDiv5() throws Exception {
		assertEquals("Buzz", sut.fizzBuzz(5));
		assertEquals("Buzz", sut.fizzBuzz(10));
		assertEquals("Buzz", sut.fizzBuzz(50));
	}

	@Test
	@DisplayName("return Fizz when dividable by 3")
	public void shouldReturnFizzIfDiv3() throws Exception {
		assertEquals("Fizz", sut.fizzBuzz(3));
		assertEquals("Fizz", sut.fizzBuzz(6));
		assertEquals("Fizz", sut.fizzBuzz(99));
	}

	@Test
	@DisplayName("return number in other cases")
	public void shouldReturnVal() throws Exception {
		assertEquals("2", sut.fizzBuzz(2));
		assertEquals("8", sut.fizzBuzz(8));
		assertEquals("11", sut.fizzBuzz(11));
	}
}

Oczywiście same nazwy to nie wszystko. Pozwalają one zaszaleć na poziomie generowania raportów, ale dopiero w połączeniu z tagami możemy odfiltrować testy.

Tagi i filtrowanie

Filtrowanie testów zarówno w JUnit 4 jak i w TestNG odbywało się na podstawie nazwy klasy. Dodatkowo TestNG udostępniał mechanizm grup testów, które pozwalały na zbieranie testów razem:

Listing 3. Mechanizm grup z TestNG

public class FizzBuzzTestNGGroupedTest {

	private FizzBuzz sut;

	@BeforeTest
	public void setup() {
		sut = new FizzBuzz();
	}

	@Test(groups = {"3", "5"})
	public void shouldReturnFizzBuzzIfDiv3And5() throws Exception {
		assertEquals("FizzBuzz", sut.fizzBuzz(15));
	}

	@Test(groups = {"5"})
	public void shouldReturnBuzzIfDiv5() throws Exception {
		assertEquals("Buzz", sut.fizzBuzz(5));
	}

	@Test(groups = {"3"})
	public void shouldReturnFizzIfDiv3() throws Exception {
		assertEquals("Fizz", sut.fizzBuzz(3));
	}

	@Test
	public void shouldReturnVal() throws Exception {
		assertEquals("2", sut.fizzBuzz(2));
	}
}

Test z grupy 3 i grupy 5 można uruchomić oddzielnie. Możemy też uzależnić uruchomienie pewnych testów od tego czy testy z jakiejś grupy przeszły. To jest bardzo potężny mechanizm. JUnit 5 podszedł do problemu trochę inaczej. Wprowadzając adnotację @Tag i dając możliwość, filtrowania testów po tej adnotacji.

Listing 4. Mechanizm tagów z JUnit 5

public class FizzBuzzJUnit5TagsTest {

	private FizzBuzz sut;

	@BeforeEach
	public void setup() {
		sut = new FizzBuzz();
	}

	@Test
	@Tag("3")
	@Tag("5")
	public void shouldReturnFizzBuzzIfDiv3And5() throws Exception {
		assertEquals("FizzBuzz", sut.fizzBuzz(15));
	}

	@Test
	@Tag("5")
	public void shouldReturnBuzzIfDiv5() throws Exception {
		assertEquals("Buzz", sut.fizzBuzz(5));
	}

	@Test
	@Tag("3")
	public void shouldReturnFizzIfDiv3() throws Exception {
		assertEquals("Fizz", sut.fizzBuzz(3));
	}

	@Test
	public void shouldReturnVal() throws Exception {
		assertEquals("2", sut.fizzBuzz(2));
	}
}

Proces filtrowania odbywa się w trakcie wykrywania testów obecnych w classpath. Zatem o ich uruchomieniu lub nie, decydujemy na poziomie silnika testów. Oczywiście, jeżeli chcemy użyć mavena, by uruchomić tylko wybrane testy, to możemy to zrobić:

Listing 5. Konfiguracja mavena

<plugin><artifactid>maven-surefire-plugin</artifactid><version>2.19</version><configuration><properties><excludetags>3</excludetags></properties></configuration></plugin>

Przy czym, mechanizm ten nie jest aż tak elastyczny jak grupy w TestNG. Jeżeli jednak dodamy odpowiedni silnik…