Gostaria de compartilhar minha experiência na criação de um sistema de automação de testes funcionais na linguagem Kotlin .
A base para criar / configurar / iniciar / monitorar a execução de testes será o jovem framework Kotest (antigo Teste Kotlin ) que está ganhando popularidade .
Depois de analisar todas as opções populares para Kotlin, descobriu-se que existem apenas duas opções "nativas":
Ou um número infinito do mundo Java: Junit4 / 5, TestNG, Cucumber JVM ou outras estruturas BDD.
A escolha recaiu sobre o Kotest com mais "curtidas" no GitHub do que Spek.
Não há muitos tutoriais sobre automação de teste no Kotlin, especialmente em combinação com o Kotest (ainda).
Acho uma boa ideia escrever uma série de artigos sobre o Kotest, bem como sobre como organizar um projeto de autoteste, construção, lançamento e tecnologias relacionadas.
— , -, 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 .
, :
— Execution ( Project)
— Spec
. cucumber — Feature
— Top Level Test
. cucumber — Scenario
— Nested Test
, .
: (), (
), (
).
cucumber — Step
— Nested Step
,@Step
Allure
.TestCase
.
- ( ) — , , .
Kotest , 4 - - Nested Test
— .
Review 1 — 4.
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
}
}
}
- Idea
-
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)
}
assertSoftly { code }
Soft Assert assertionsKotest
— .response.asClue { }
MUST HAVE . Scope kotlinasClue
—response
Matchers
MatchersKotest
— , .
shouldBe
— infix .
shouldBeBlank
— infix (.. ) .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
- — ,
callback — FreeSpec
, :
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
beforeSpec
FreeSpec
, —Spec
beforeContainer
TestType.Container
, —TestCase
beforeEach
()TestType.Test
, —TestCase
( )beforeAny
TestType.Container
TestType.Test
, —TestCase
beforeTest
TestCase
,TestType
.
beforeAny
. (TestType
) (TestType
)
after
afterTest
beforeTest
.
—TestCase
+TestResult
afterAny
beforeAny
.
—TestCase
+TestResult
afterEach
beforeEach
.
—TestCase
+TestResult
afterContainer
beforeContainer
.
—TestCase
+TestResult
afterSpec
beforeSpec
.
—Spec
finalizeSpec
.
— KClass<out Spec>
+ Map<TestCase, TestResult>
Kotest callback .
:
beforeAll
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
}
}
}
}
, ()
- , — .
-
Given server is up
, — . -
forAll
.Row
, . -
row
.
io.kotest.data.rows.kt
22 - .
, Property Based Testing ( ) - :
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.io — 4.2.0
readme.md github.
, 'Kotlin. ':
- Kotest. , , , , Property Based Testing
- Spring Test. Kotest. .
- Esperança de expectativas. Retrofit para teste de API. Trabalhando com DB por meio do Spring Data Jpa.
- Gradle. Estrutura escalável e distribuída de muitos projetos de teste automático.
- Gestão do ambiente. TestContainers, plug-in gradle compose, kubernetes java api + helm