
Saya ingin berbagi pengalaman saya dalam membuat sistem otomasi pengujian fungsional dalam bahasa Kotlin .
Dasar untuk membuat / mengonfigurasi / meluncurkan / memantau eksekusi pengujian adalah framework Kotest muda (sebelumnya Kotlin Test ) yang mulai populer .
Setelah menganalisis semua opsi populer untuk Kotlin, ternyata hanya ada dua opsi "asli":
Atau jumlah tak terbatas dari dunia Java: Junit4 / 5, TestNG, Cucumber JVM atau framework BDD lainnya.
Pilihan jatuh pada Kotest dengan lebih banyak "suka" di GitHub daripada Spek.
Tidak banyak tutorial tentang otomatisasi pengujian di Kotlin, terutama jika digabungkan dengan Kotest (belum).
Menurut saya, menulis serangkaian artikel tentang Kotest, serta tentang mengatur proyek uji otomatis, membangun, meluncurkan, dan teknologi terkait adalah ide yang bagus.
Akibatnya, Anda akan mendapatkan panduan lengkap - cara membuat sistem, atau bahkan ekosistem, untuk mengotomatiskan pengujian fungsional dalam bahasa Kotlin dan platform Kotest.
Minus
Segera, kami akan menentukan kerugian global dan mempertimbangkan bahwa proyek berkembang pesat. Masalah yang ada dalam versi saat ini pada saat penulisan 4.2.5ini sudah dapat diperbaiki dalam rilis baru.
, , .
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
,@StepAllure.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—responseMatchers
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 :
TestListenerProjectListener
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, —SpecbeforeContainer
TestType.Container, —TestCasebeforeEach
()TestType.Test, —TestCase( )beforeAny
TestType.ContainerTestType.Test, —TestCasebeforeTest
TestCase,TestType.
beforeAny. (TestType) (TestType)
after
afterTest
beforeTest.
—TestCase+TestResultafterAny
beforeAny.
—TestCase+TestResultafterEach
beforeEach.
—TestCase+TestResultafterContainer
beforeContainer.
—TestCase+TestResultafterSpec
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.kt22 - .
, 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. .
- Harapan Menunggu. Retrofit untuk pengujian API. Bekerja dengan DB melalui Spring Data Jpa.
- Gradle. Struktur skalabel dan terdistribusi dari banyak project pengujian otomatis.
- Pengelolaan lingkungan. TestContainers, plugin gradle compose, kubernetes java api + helm