科特林 测试自动化(第1部分)。Kotest:开始

科斯特



我想分享一下我用Kotlin语言创建功能测试自动化系统的经验



创建/配置/启动/监视测试执行的基础将是日渐流行的年轻的Kotest框架(以前称为Kotlin Test)。

在分析了Kotlin的所有流行选项之后,结果发现只有两个“本机”选项:





或来自Java世界的无限数量:Junit4 / 5,TestNG,Cucumber JVM或其他BDD框架。



这次选择落在Kotest上,GitHub上的“点赞”次数比Spek多。



Kotlin中关于测试自动化的教程并不多,特别是与Kotest结合使用时。



我认为最好写一系列有关Kotest的文章,以及有关组织自动测试项目,构建,启动和相关技术的文章。



因此,您应该获得一个宽敞的指南-如何创建系统,甚至是生态系统,以使用Kotlin语言和Kotest平台自动执行功能测试。



缺点



立即,我们将确定全球不利因素,并考虑到该项目正在迅速发展。在撰写本文时,当前版本中的那些问题4.2.5已经可以在新发行版中解决。



, , .

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. .
  • 期望等候。进行API测试的改造。通过Spring Data Jpa使用DB。
  • 摇篮。许多自动测试项目的可扩展和分布式结构。
  • 环境管理。TestContainers,gradle撰写插件,kubernetes java api + helm



All Articles