Je souhaite partager mon expérience de création d'un système d'automatisation de tests fonctionnels dans le langage Kotlin .
La base pour créer / configurer / lancer / surveiller l'exécution des tests sera le jeune framework Kotest (anciennement Kotlin Test ) qui gagne en popularité .
Après avoir analysé toutes les options populaires pour Kotlin, il s'est avéré qu'il n'y avait que deux options "natives":
Ou un nombre infini du monde Java: Junit4 / 5, TestNG, Cucumber JVM ou autres frameworks BDD.
Le choix s'est porté sur Kotest avec plus de "likes" sur GitHub que sur Spek.
Il n'y a pas beaucoup de tutoriels sur l'automatisation des tests dans Kotlin, surtout lorsqu'ils sont combinés avec Kotest (encore).
Je pense que c'est une bonne idée d'écrire une série d'articles sur Kotest, ainsi que sur l'organisation d'un projet de test automatique, la construction, le lancement et les technologies associées.
— , -, 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. .
- Attentes Attentes. Retrofit pour les tests d'API. Travailler avec DB via Spring Data Jpa.
- Gradle. Structure évolutive et distribuée de nombreux projets de test automatique.
- Gestion de l'environnement. TestContainers, plugin Gradle Compose, API java kubernetes + helm