Mobile, QA

Kotlin – testowanie: frameworki, mockowanie, asercje. Przegląd możliwości w 2020 roku

kotlin testowanie

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:

  1. Frameworki
  2. Mocki
  3. Asercje

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:

  1. Wspiera suspend functions
  2. 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"

Strikt

expectThat(subject)
 .hasLength(35)
 .matches(Regex("[\w\s]+"))
 .startsWith("T")

Atrium

expect(x).toBe(9)

HamKrest

assertThat("xyzzy", startsWith("x") and endsWith("y") and !containsSubstring("a"))Expekt

Expekt

23.should.equal(23)
"Kotlin".should.not.contain("Scala")
listOf(1, 2, 3).should.have.size.above(1)

AssertK

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.

Android Developer z zamiłowaniem do biznesu i zarządzania

Chce tworzyć wspaniałe produkty w szeroko pojętym obszarze mobile. Telefony komórkowe to obecnie zdalne piloty naszego życia, róbmy je dobrze! Zawsze na pierwszym miejscu stawia użytkownika – zarówno ostatecznego użytkownika aplikacji, jak i tego, który w przyszłości będzie przeglądał kod. Fanatyk czystej architektury, wyznawca SOLID, fanboy Kotlin’a.

Podobne artykuły

[wpdevart_facebook_comment curent_url="https://justjoin.it/blog/kotlin-testowanie-frameworki-mockowanie-asercje-przeglad-mozliwosci-w-2020-roku" order_type="social" width="100%" count_of_comments="8" ]