Kotlin. Automatización de pruebas (parte 1). Kotest: el comienzo

Kotest



Quiero compartir mi experiencia en la creación de un sistema de automatización de pruebas funcionales en el lenguaje Kotlin .



La base para crear / configurar / lanzar / monitorear la ejecución de las pruebas será el marco joven Kotest (anteriormente Kotlin Test ) que está ganando popularidad .

Después de analizar todas las opciones populares para Kotlin, resultó que solo hay dos opciones "nativas":





O un número infinito del mundo Java: Junit4 / 5, TestNG, Cucumber JVM u otros frameworks BDD.



La elección recayó en Kotest con más "Me gusta" en GitHub que Spek.



No hay muchos tutoriales sobre la automatización de pruebas en Kotlin, especialmente en combinación con Kotest (todavía).



Creo que es una buena idea escribir una serie de artículos sobre Kotest, así como sobre la organización de un proyecto de autoprueba, construcción, lanzamiento y tecnologías relacionadas.



Como resultado, debe obtener una guía amplia: cómo crear un sistema, o incluso un ecosistema, para automatizar las pruebas funcionales en el lenguaje Kotlin y la plataforma Kotest.



Desventajas



Inmediatamente, determinaremos las desventajas globales y tendremos en cuenta que el proyecto se está desarrollando rápidamente. Los problemas que se encuentran en la versión actual en el momento de escribir este artículo 4.2.5ya se pueden solucionar en nuevas versiones.



, , .

2020 KotlinTest, 4.0.0 , Idea Kotest, -.



4.0.7 4.1 , , , 4.0 4.1.

Java — - JS.



.



.

. data-driven property-based .

.



allure ( , , DSL ).



.



Kotest?



Kotlin Junit4 Junit5.



— , , , @SpringBootTest, @Test, before beforeClass .



e2e .



, , .



Kotest :



  • BDD Kotlin DSL ,
  • data driven
  • DSL .
  • (, junit)


  • , . GitHub




?



Kotest DSL .



String Spec — unit- .



- - : Gherkin, , .



FreeSpec.

Kotest BDD , Gherkin (Cucumber).



FreeStyle , , code-style, best practice, Merge-Request`.





5 ( ) Kotest .



, :



  1. — Execution ( Project)



  2. — Spec

    . cucumber — Feature



  3. — Top Level Test

    . cucumber — Scenario



  4. — Nested Test

    , .

    : (), (), ().

    cucumber — Step



  5. — Nested Step

    , @Step Allure. TestCase .

    - ( ) — , , .





Kotest , 4 - - Nested Test — .



Review 14.



Gherkin (Scenario Template) — Data Driven.

Kotest 3. - Top Level Test, — .





REST API .



, , , , .



:



open class KotestFirstAutomatedTesting : FreeSpec() {

    private companion object {
        private val log = LoggerFactory.getLogger(KotestFirstAutomatedTesting::class.java)
    }

    init {
        "Scenario. Single case" - {
            val expectedCode = 200

            "Given server is up" { }

            "When request prepared and sent" { }

            "Then response received and has $expectedCode code" { }
        }
    }
}


Gherkin



-, , , (FreeSpec). .



, Kotlin DSL — type-safe builder, / / pre after / .



"Then response received and has $expectedCode code"



DSL



.


! !

FreeSpec, FreeSpecRootScope:



abstract class FreeSpec(body: FreeSpec.() -> Unit = {}) : DslDrivenSpec(), FreeSpecRootScope


FreeSpecRootScope String -:



infix operator fun String.minus(test: suspend FreeScope.() -> Unit) { }


"Scenario. Single case" - { } String.minus, FreeScope .



, Kotlin, - , .





FreeSpecRootScope String invoke



infix operator fun String.invoke(test: suspend TestContext.() -> Unit) { }


"string" { } TestContext.





:



init {
        "Scenario. Single case" - {

            //region Variables
            val expectedCode = 200
            val testEnvironment = Server()
            val tester = Client()
            //endregion

            "Given server is up" {
                testEnvironment.start()
            }

            "When request prepared and sent" {
                val request = Request()
                tester.send(request)
            }

            lateinit var response: Response
            "Then response received" {
                response = tester.receive()
            }

            "And has $expectedCode code" {
                response.code shouldBe expectedCode
            }
        }
    }




  1. Idea
  2. lateinit var response: Response, ,


Kotest Assertions Matchers



Kotest Assertions and Matchers.



testImplementation "io.kotest:kotest-assertions-core:$kotestVersion" Matcher-, SoftAssertion Assertion .



Matcher-, .



:



"And has $expectedCode code" {
    assertSoftly {
        response.asClue {
            it.code shouldBe expectedCode
            it.body.shouldNotBeBlank()
        }
    }
    val assertion = assertThrows<AssertionError> {
        assertSoftly {
            response.asClue {
                it.code shouldBe expectedCode + 10
                it.body.shouldBeBlank()
            }
        }
    }
    assertion.message shouldContain "The following 2 assertions failed"
    log.error("Expected assertion", assertion)
}


  1. assertSoftly { code }

    Soft Assert assertions Kotest — .
  2. response.asClue { }

    MUST HAVE . Scope kotlin asClue response
  3. Matchers

    Matchers Kotest — , .

    shouldBe — infix .

    shouldBeBlank — infix (.. ) .
  4. assertThrows<AssertionError>

    Junit5

    inline fun <reified T : Throwable> assertThrows(noinline executable: () -> Unit) — ,


pre / after



.

(4.3.5) io.kotest.core.spec.CallbackAliasesKt kotest-framework-api-jvm typealias:



typealias BeforeTest = suspend (TestCase) -> Unit
typealias AfterTest = suspend (Tuple2<TestCase, TestResult>) -> Unit
typealias BeforeEach = suspend (TestCase) -> Unit
typealias AfterEach = suspend (Tuple2<TestCase, TestResult>) -> Unit
typealias BeforeContainer = suspend (TestCase) -> Unit
typealias AfterContainer = suspend (Tuple2<TestCase, TestResult>) -> Unit
typealias BeforeAny = suspend (TestCase) -> Unit
typealias AfterAny = suspend (Tuple2<TestCase, TestResult>) -> Unit
typealias BeforeSpec = suspend (Spec) -> Unit
typealias AfterSpec = suspend (Spec) -> Unit
typealias AfterProject = () -> Unit
typealias PrepareSpec = suspend (KClass<out Spec>) -> Unit
typealias FinalizeSpec = suspend (Tuple2<KClass<out Spec>, Map<TestCase, TestResult>>) -> Unit
typealias TestCaseExtensionFn = suspend (Tuple2<TestCase, suspend (TestCase) -> TestResult>) -> TestResult
typealias AroundTestFn = suspend (Tuple2<TestCase, suspend (TestCase) -> TestResult>) -> TestResult
typealias AroundSpecFn = suspend (Tuple2<KClass<out Spec>, suspend () -> Unit>) -> Unit


2 , :



  • Listener
  • Extension


immutable ( after).

, - , , , .



Listener — , .

, , 2 :



  • TestListener
  • ProjectListener


callback :



  • Listener
  • Listener @AutoScan
  • — ,




callbackFreeSpec, :



 init {
        ///// ALL IN INVOCATION ORDER /////

        //// BEFORE ////
        beforeSpec { spec ->
            log.info("[BEFORE][1] beforeSpec '$spec'")
        }
        beforeContainer { onlyContainerTestType ->
            log.info("[BEFORE][2] beforeContainer onlyContainerTestType '$onlyContainerTestType'")
        }
        beforeEach { onlyTestCaseType ->
            log.info("[BEFORE][3] beforeEach onlyTestCaseType '$onlyTestCaseType'")
        }
        beforeAny { containerOrTestCaseType ->
            log.info("[BEFORE][4] beforeAny containerOrTestCaseType '$containerOrTestCaseType'")
        }
        beforeTest { anyTestCaseType ->
            log.info("[BEFORE][5] beforeTest anyTestCaseType '$anyTestCaseType'")
        }

        //// AFTER ////
        afterTest { anyTestCaseTypeWithResult ->
            log.info("[AFTER][1] afterTest anyTestCaseTypeWithResult '$anyTestCaseTypeWithResult'")
        }
        afterAny { containerOrTestCaseTypeAndResult ->
            log.info("[AFTER][2] afterAny containerOrTestCaseTypeAndResult '$containerOrTestCaseTypeAndResult'")
        }
        afterEach { onlyTestCaseTypeAndResult ->
            log.info("[AFTER][3] afterEach onlyTestCaseTypeAndResult '$onlyTestCaseTypeAndResult'")
        }
        afterContainer { onlyContainerTestTypeAndResult ->
            log.info("[AFTER][4] afterContainer onlyContainerTestTypeAndResult '$onlyContainerTestTypeAndResult'")
        }
        afterSpec { specWithoutResult ->
            log.info("[AFTER][5] afterSpec specWithoutResult '$specWithoutResult'")
        }

        //// AT THE END ////
        finalizeSpec {specWithAllResults ->
            log.info("[FINALIZE][LAST] finalizeSpec specWithAllResults '$specWithAllResults'")
        }

        "Scenario" - { }
}


.



before


  1. beforeSpec

    FreeSpec , — Spec
  2. beforeContainer

    TestType.Container, — TestCase
  3. beforeEach

    () TestType.Test, — TestCase ( )
  4. beforeAny

    TestType.Container TestType.Test, — TestCase
  5. beforeTest

    TestCase , TestType .

    beforeAny. ( TestType) ( TestType)


after


  1. afterTest

    beforeTest .

    TestCase + TestResult
  2. afterAny

    beforeAny .

    TestCase + TestResult
  3. afterEach

    beforeEach .

    TestCase + TestResult
  4. afterContainer

    beforeContainer .

    TestCase + TestResult
  5. afterSpec

    beforeSpec .

    Spec


finalizeSpec


.

KClass<out Spec> + Map<TestCase, TestResult>





Kotest callback .

:



  1. beforeAll

  2. afterAll



ProjectListener , AbstractProjectConfig Project.

AbstractProjectConfig — :



object ProjectConfig : AbstractProjectConfig() {
    private val log = LoggerFactory.getLogger(ProjectConfig::class.java)

    override fun beforeAll() {
        log.info("[BEFORE PROJECT] beforeAll")
    }

    override fun afterAll() {
        log.info("[AFTER PROJECT] afterAll")
    }
}


Data Driven Test



io.kotest.data Data Driven Testing



c Data Provider-:



init {
        "Scenario. Single case" - {

            val testEnvironment = Server()
            val tester = Client()

            "Given server is up. Will execute only one time" {
                testEnvironment.start()
            }

            forAll(
                    row(1, UUID.randomUUID().toString()),
                    row(2, UUID.randomUUID().toString())
            ) { index, uuid ->

                "When request prepared and sent [$index]" {
                    tester.send(Request(uuid))
                }

                "Then response received [$index]" {
                    tester.receive().code shouldBe 200
                }
            }
        }
    }


, ()



  1. , — .
  2. Given server is up , — .
  3. forAll. Row , .
  4. row .

    io.kotest.data.rows.kt 22 - .

    , Property Based Testing ( )
  5. :

    forAll(
    row(1, UUID.randomUUID().toString()),
    row(2, UUID.randomUUID().toString())
    ) { index, uuid -> block }


2 .

2 . , 2 .



.

[$index].

uuid — .





-, qa-kotest-articles/kotest-first.



.



, , .



junit , junit Idea.



Idea .



Data Driven .



Groovy Spoke, Kotlin.



kotest.io4.2.0 readme.md github.





, 'Kotlin. ':



  • Kotest. , , , , Property Based Testing
  • Spring Test. Kotest. .
  • Expectativas Espera. Actualización para pruebas de API. Trabajando con DB a través de Spring Data Jpa.
  • Gradle. Estructura escalable y distribuida de muchos proyectos de pruebas automáticas.
  • Gestión medioambiental. TestContainers, complemento de composición de gradle, kubernetes java api + helm



All Articles