Kotlin – testowanie: frameworki, mockowanie, asercje. Przegląd możliwości w 2020 roku
Poniżej przedstawiam przegląd dostępnych narzędzi do testowania w Kotlinie w 2020 roku. Patrzę przez swój pryzmat – programowania na Androida, ale informacje zawarte w artykule można wykorzystać w każdym projekcie, który jest pisany w Kotlinie.
Narzędzia możemy podzielić na trzy główne kategorie:
- Frameworki
- Mocki
- Asercje
Spis treści
1. Kotlin frameworki
JUnit4
Po prostu wystarczająco dobre do pracy. Bez żadnych fajnych funkcji. Ciągle domyślne narzędzie do testowania w świecie Androida. Przede wszystkim dlatego, że kolejna jego wersja (JUnit5) wymaga co najmniej Javy 8, która również nie jest domyślna, ponieważ wpłynęłoby to negatywnie na czas kompilacji.
JUnit5
To przyszłość! Bez problemu można mieć, w jednym projekcie, testy JUnit5 razem ze starymi testami w JUnit4. Również wszystkie pozostałe frameworki, wspomniane tutaj, będą wymagały JUnit5 do działania. Właściwie to JUnit5 jest wystarczający do osiągnięcia wszystkiego, czego potrzebujesz. Dostajesz coś, co znasz z JUnit4, plus możliwość korzystania ze wszystkich nowych funkcji. Osobiście nie potrzebuję nic więcej.
Najfajniejsze funkcje:
Zagnieżdżone testy – Pozwalają budować piękne hierarchie testów i grupować je tak jak chcesz. Na przykład możesz pogrupować testy zgodnie z zasadami BDD na GIVEN, WHEN, THEN:
@Nested inner class `GIVEN (...)`{ @Nested inner class `WHEN (...)`{ @Test fun `THEN (...)`() { @Test fun `THEN (...)`() {
assertAll – sprawdź wszystkie asercje zanim rzucisz błąd. Nie będziesz musiał powtarzać testu, żeby sprawdzić pozostałe warunki!
assertAll( { assertTrue(...) }, { assertFalse(..) } )
assertThrows – testuj rzucanie exception jak człowiek. Na końcu ze wszystkimi asercjami.
assertThrows<NullPointerException> { nullPointerFunc() }
@ParameterizedTest – koniec pisania różnych scenariuszy testów metodą Copy&Paste. Poczuj moc parametrów, a dodatkowo wykorzystaj Kotlinowe data classy.
@ParameterizedTest @ValueSource(strings = ["racecar", "radar", "able was I ere I saw elba"]) fun palindromes(candidate: String?) { assertTrue(StringUtils.isPalindrome(candidate)) }
@DisplayName – w Kotlinie możesz używać zdań jako nazw, ale dzięki DisplayName możesz używać emoji! Tak, prawdopodobnie nigdy tego nie zrobisz, ale fajny bajer.
@Test @DisplayName("Test name with emoji: ") fun testWithDisplayNames() {
Dodatkowe informacje:
1. Żeby używać wszystkich tych błogosławieństw na Androidzie musisz dodać zewnętrzny plugin: https://github.com/mannodermaus/android-junit5.
2. JUnit5 nie używa znanych wcześniej @Rule
, zamiast tego są rozszerzenia, ale przejście na nie jest bardzo proste:
@ExtendWith( MockitoExtension::class, //Mockito have extensions InstantExecutorExtension::class //Android have only rule, but rewrite it as extension is dead simply ) //Here you have extension class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback { override fun beforeEach(context: ExtensionContext?) { ArchTaskExecutor.getInstance() .setDelegate( object : TaskExecutor() { override fun executeOnDiskIO(runnable: Runnable) = runnable.run() override fun postToMainThread(runnable: Runnable) = runnable.run() override fun isMainThread(): Boolean = true } ) } override fun afterEach(context: ExtensionContext?) { ArchTaskExecutor.getInstance().setDelegate(null) } }
Spek
Główną koncepcją Speka jest używanie zagnieżdżonych lambd. Dodatkowo promuje ona pisanie testów w stylu BDD, co jest oczywiście dobre, ale wcale ich nie potrzebujesz, żeby pisać tak testy.
object CoreConceptTest: Spek({ group("a group") { test("a test") { } group("a nested group") { test("another test") { } } } }) object BddStyleTest : Spek({ Feature("Tested feature") { Scenario("Test scenario") { Given("...") { } When("...") { } Then("...") { }
Kotest
Bardzo podobne podejście do powyższego, ale Kotest daje Ci to samo i jeszcze dużo więcej!
class SentencesAsTests : StringSpec({ "Some test nr 1" { } "Some test nr 2" { } }) class BddStyleTest : BehaviorSpec({ given("...") { `when`("...") { then("...") { } } `when`("...") {
Lista możliwych sposobów pisania testów jest długa:
2. Mockowanie
Mockito
Najpopularniejsze narzędzie do mockowania. Napisane w Javie, nic nowego, ale ciągle bardzo dobre i rozwijane narzędzie. Nawet dla projektów w Kotlinie. Dodatkowo dla Kotlina stworzono specjalną bibliotekę pomocniczą, która pozwala Ci zastąpić wywołania when
, bardziej normalnym whenever
:
https://github.com/nhaarman/mockito-kotlin
W gruncie rzeczy posiada wszystko czego potrzebujesz. Prawie. Nie daje pełnego wsparcie dla suspend
functions. Będziesz mieć problem, gdy będziesz chciał manipulować osią czasu w zwrotce mockowanej funkcji.
Istnieje otwarty PR na obsługę supsend answers:
https://github.com/nhaarman/mockito-kotlin/pull/357
Na tę chwilę, gdy używasz Coroutines w swoim projekcie, łatwiej będzie Ci używać MockK.
MockK
Również bardzo dobre narzędzie do mockowanie, ale przede wszystkim napisane całkowicie w Kotlinie.
MockK ma dwie główne zalety w porównaniu do Mockito:
- Wspiera
suspend
functions - Działa z Kotlin Multiplatform
W związku z tym wybieram MockK jako moje podstawowe narzędzie w każdym nowym projekcie.
val mock = mockk<ObjectToMock>() every { mock.function() } returns value //simple mock coEvery { mock.suspendFunction() } returns value //suspend mock
3. Asercje
Tutaj lista potencjalnych narzędzi jest bardzo długa. Przykłady wykorzystania oddadzą więcej niż 1000 słów, bo tu głównie chodzi o składnie:
Kotest – tak, ci sami ludzie, co tworzą framework, mają oddzielną bibliotekę do asercji. To najbardziej popularna biblioteka do asercji:
name shouldBe "sam" user.name shouldNotBe null
Kluent – bardzo podobne narzędzie do poprzedniego, drugie pod względem popularności:
"hello" shouldBeEqualTo "hello" "hello" shouldNotBeEqualTo "world"
expectThat(subject) .hasLength(35) .matches(Regex("[\w\s]+")) .startsWith("T")
expect(x).toBe(9)
assertThat("xyzzy", startsWith("x") and endsWith("y") and !containsSubstring("a"))Expekt
23.should.equal(23) "Kotlin".should.not.contain("Scala") listOf(1, 2, 3).should.have.size.above(1)
assertThat(person.name).isEqualTo("Alice")
W tej grupie miałem największy problem z wyborem czegoś dla siebie. Osobiście czuję, że nie potrzebuję niczego więcej, niż to co daje JUnit w swoim pakiecie. Gdybym musiał wybrać, pewnie zdecydowałbym się na Kotest, ponieważ ładnie wygląda i jest najbardziej popularny. Popularność daje większą pewność, że będzie utrzymywany.
W mojej skromnej opinii to tylko kwestia gustu, którą bibliotekę chciałbyś wybrać.
Podsumowanie
Mam nadzieję, że pomogłem wam zrobić szybki przegląd możliwości wykorzystania narzędzi do testowania w Kotlinie. Ja używam JUnit5 i MockK bez dodatkowych bibliotek do asercji. Podziel się w komentarzu, czego używasz w swoim projekcie? Czego chciałbyś spróbować? Pamiętaj, że to testy i tutaj możesz zaszaleć z wykorzystanymi bibliotekami!
Zdjęcie główne artykułu pochodzi z unsplash.com.