Jedną z cech testów jednostkowych jest ich powtarzalność. Najlepiej, jeżeli powtórzenie jest wykonywane automatycznie. Jednak nie o tym dziś będziemy mówić. Czasami pisząc testy, musimy uwzględnić, że nasz kod uruchamiany jest w środowisku wielowątkowym. Nie ma w tym nic nadzwyczajnego. Podobnie jest w przypadku, gdy tworzymy testy, których zadaniem jest jakiegoś kodu w wielu kopiach jednocześnie. Dla mnie wzorcową implementacją tego zachowania jest TestNG:

Listing 1. Testy powtarzalne w TestNG

@Test
public class FizzBuzzTestNGRepeatedTest {

	private FizzBuzz sut;

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

	@Test(invocationCount = 100, threadPoolSize = 4)
	public void shouldReturnFizzBuzzIfDiv3And5() throws Exception {
		assertEquals("FizzBuzz", sut.fizzBuzz(15));
	}

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

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

	@Test(invocationCount = 100, threadPoolSize = 4)
	public void shouldReturnVal() throws Exception {
		assertEquals("2", sut.fizzBuzz(2));
	}

}

Mamy tu dość dużą elastyczność w konfiguracji testów. Po pierwsze za pomocą parametru invocationCount, możemy określić, ile testów ma zostać wykonanych. Po drugie parametr threadPoolSize pozwala nam na skonfigurowanie liczby wątków użytych do uruchomienia testów.

W przypadku JUnit 5 wygląda to trochę inaczej. Po pierwsze wprowadzono adnotację @RepeatedTest, która pozwala na określenie liczby powtórzeń. Adnotacja ta zachowuje się jak specjalizowana @Test.

Listing 2. Testy powtarzalne w JUnit 5

public class FizzBuzzJUnit5RepeatedTest {

	private FizzBuzz sut;

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

	@RepeatedTest(value = 100, name = "Repetition {currentRepetition} of {totalRepetition}")
	public void shouldReturnFizzBuzzIfDiv3And5() throws Exception {
		assertEquals("FizzBuzz", sut.fizzBuzz(15));
	}

	@RepeatedTest(value = 100, name = "Repetition {currentRepetition} of {totalRepetition}")
	public void shouldReturnBuzzIfDiv5() throws Exception {
		assertEquals("Buzz", sut.fizzBuzz(5));
	}

	@RepeatedTest(value = 100, name = "Repetition {currentRepetition} of {totalRepetition}")
	public void shouldReturnFizzIfDiv3() throws Exception {
		assertEquals("Fizz", sut.fizzBuzz(3));
	}

	@RepeatedTest(value = 100, name = "Repetition {currentRepetition} of {totalRepetition}")
	public void shouldReturnVal() throws Exception {
		assertEquals("2", sut.fizzBuzz(2));
	}
}

Po drugie, adnotacja ta ma parametr name, który pozwala na dodanie nazwy specyficznej dla uruchomienia. W nazwie tej możemy wykorzystać trzy flagi:

  • {currentRepetition} – w której jest numer aktualnego przebiegu,
  • {totalRepetition} – w której mamy ilość wszystkich przebiegów,
  • {displayName} – która zawiera nazwę testu zdefiniowaną w @DisplayName.

Możemy też jako wartość parametru podać jedną ze stałych zdefiniowanych w samej adnotacji:

  • SHORT\_DISPLAY\_NAME – która jest równoważna „repetition {currentRepetition} of {totalRepetition}”,
  • LONG\_DISPLAY\_NAME – która jest równoważna „{displayName} :: repetition {currentRepetition} of {totalRepetition}”.

Problem polega jednak na tym, że nie możemy skonfigurować liczby wątków, które zostaną wykorzystane do uruchomienia testu. Nie pozostaje nic innego jak użycie specyficznego silnika, którego oczywiście jeszcze nie ma.