JUnit5 ile Parameterized Test
Bu yazımızda unit testlerimizi daha efektif hale getirmeye çalışacağız. Peki neden? Çünkü testlerimizi yazarken muhtemelen fark ettiğiniz bir problem var.
Bazı durumlarda test methodumuzun işlevi aynı kalmasını istiyoruz ama farklı değerler çalışmasını istiyoruz. Bu sorunu test methodlarımızı çoklayarak da yapabiliriz.
Fakat neden kod tekrarı yapalım ki? Ayrıca proje kodlarına değer verdiğimiz kadar test kodlarımıza da değer vermeliyiz. Unutmamalıyız ki test methodlarımız ayrıca bizler için
dokümantasyon da sağlar. Bilmediğimiz bir methodun nasıl davrandığını test methodlarından çok daha iyi anlayabiliriz.
Parameterized Tests
Parametreli testler, bir testi farklı argümanlarla birden çok kez çalıştırmanızı sağlar. @Test
metodları gibi tanımlanan ancak @ParameterizedTest
annotasyonu kullanılan bir JUnit özelliğidir. Ayrıca, her çağrı için argümanları sağlayacak en az bir parametre kaynağı(source) belirtmeniz ve test metodunda bu argümanları kullanmanız gerekir. Aksi halde parametreli test zaten olmaz.
Aşağıdaki örnek, @ValueSource
annotasyonunu kullanarak bir dizi String’i argüman olarak belirleyen parametreli bir testi göstermektedir.
1 2 3 4 5 6 7 8 9 |
@ParameterizedTest @ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" }) void palindromes(String candidate) { assertTrue(StringUtils.isPalindrome(candidate)); } |
1 2 3 4 5 6 7 8 |
palindromes(String) ✔ ├─ [1] candidate=racecar ✔ ├─ [2] candidate=radar ✔ └─ [3] candidate=able was I ere I saw elba ✔ |
@ValueSource
Aşağıdaki tipler parametreli testler için parametre olarak verilebilir.
- short
- byte
- int
- long
- float
- double
- char
- boolean
- java.lang.String
- java.lang.Class
1 2 3 4 5 6 7 8 9 |
@ParameterizedTest @ValueSource(ints = { 1, 2, 3 }) void testWithValueSource(int argument) { assertTrue(argument > 0 && argument < 4); } |
@NullSource & @EmptySource
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@ParameterizedTest @EmptySource void shouldEqualsEmptyValue(String value) { assertThat(StringUtils.EMPTY).isEqualTo(value); } @ParameterizedTest @NullSource void shouldEqualsNullValue(String value) { assertNull(value); } |
@EnumSource
Enum değerlerini parametre vermek için kullanılır.
Örnek bir method üzerinden inceleyelim. Amacımız sadece test yazabilmek olduğu için methodumuz uydurma 🙂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
@Service @RequiredArgsConstructor @Slf4j public class AdvertService { private final RabbitMqService rabbitMqService; private final AdvertRepository advertRepository; public void getAdvertsWithoutAdvertisement(UserType type) { if (Set.of(UserType.INDIVIDUAL, UserType.STUDENT).contains(type)) { log.error("{} type is not valid", type); throw new RuntimeException("user type is not valid"); } advertRepository.findAll(); } } |
UserType göre görevini yapan bir methodumuz var ve UserType.INDIVIDUAL, UserType.STUDENT enum değerlerine göre hata fırlatmasını bekliyoruz ve “PREMIUM”, “CORPORATE” değerleri geldiğinde ise görevini başarılı bir şekilde gerçekleştirmesini bekliyoruz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
@ExtendWith(MockitoExtension.class) class AdvertServiceTest { @InjectMocks private AdvertService advertService; @Mock private RabbitMqService rabbitMqService; @Mock private AdvertRepository advertRepository; @ParameterizedTest @EnumSource(value = UserType.class, names = {"STUDENT", "INDIVIDUAL"}) void shouldThrowException_whenUserTypeIndividualOrStudent(UserType userType) { RuntimeException exception = assertThrows(RuntimeException.class, () -> advertService.getAdvertsWithoutAdvertisement(userType)); assertThat("user type is not valid").isEqualTo(exception.getMessage()); Mockito.verifyNoInteractions(advertRepository); Mockito.verifyNoInteractions(rabbitMqService); } @ParameterizedTest @EnumSource(value = UserType.class, names = {"PREMIUM", "CORPORATE"}) void shouldGetAdverts_whenUserTypeIndividualOrStudent(UserType userType) { advertService.getAdvertsWithoutAdvertisement(userType); Mockito.verify(rabbitMqService).sendMessage(Mockito.any(EmailMessage.class)); Mockito.verify(advertRepository).findAll(); } } |
@EnumSource ile value ve names değerlerini vermemiz gerekiyor. names alanı girmediğinizde enum içerisinde tanımlı bütün değerler ile testi çalıştırmaya çalışacaktır.
Fakat biz sadece belirlediğimiz değerlere göre çalışmasını istediğimiz için names ile değerlerimizi vermemiz gerekiyor.
@MethodSource
Parametreli testlere veri sağlamak için kullanılan bir annotation.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@ParameterizedTest @MethodSource("stringProvider") void testWithExplicitLocalMethodSource(String argument) { assertNotNull(argument); } static Stream<String> stringProvider() { return Stream.of("apple", "banana"); } |
Başka bir class içinden test methodlarımıza aşağıdaki gibi parametre geçebiliriz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class ExternalMethodSourceDemo { @ParameterizedTest @MethodSource("example.StringsProviders#tinyStrings") void testWithExternalMethodSource(String tinyString) { // test with tiny string } } class StringsProviders { static Stream<String> tinyStrings() { return Stream.of(".", "oo", "OOO"); } } |
@CsvSource
Diğer source annotation gibi testlerimize veri sağlamak için kullanılıyor. Adından da anlayabileceğimiz gibi csv dosyası formatında veriyi sağlayabilirisiniz.
Verileri virgüller ile ayırarak aşağıdaki gibi bir kullanım sağlanabilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@ParameterizedTest @CsvSource({ "apple, 1", "banana, 2", "'lemon, lime', 0xF1", "strawberry, 700_000" }) void testWithCsvSource(String fruit, int rank) { assertNotNull(fruit); assertNotEquals(0, rank); } |
@CsvFileSource
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
ParameterizedTest @CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1) void testWithCsvFileSourceFromClasspath(String country, int reference) { assertNotNull(country); assertNotEquals(0, reference); } @ParameterizedTest @CsvFileSource(files = "src/test/resources/two-column.csv", numLinesToSkip = 1) void testWithCsvFileSourceFromFile(String country, int reference) { assertNotNull(country); assertNotEquals(0, reference); } @ParameterizedTest(name = "[{index}] {arguments}") @CsvFileSource(resources = "/two-column.csv", useHeadersInDisplayName = true) void testWithCsvFileSourceAndHeaders(String country, int reference) { assertNotNull(country); assertNotEquals(0, reference); } |
Kaynak
Önceki test yazıları için aşağıdaki linklerden ulaşabilirsiniz.
- https://bilisim.io/2023/06/12/junit-ile-controller-testi
- https://bilisim.io/2023/10/19/junit-ile-servis-katmani-testi-1/
- https://bilisim.io/2023/10/24/junit-ile-servis-katmani-testi-2/
- https://bilisim.io/2023/11/01/junit-ile-servis-katmani-testi-3/
- https://bilisim.io/2023/11/18/junit-ile-spring-data-repository-testi/
Faydalı olması dileğiyle.