Kotest, MockK, coroutine testi, property-based testing ve Kover coverage ile Kotlin test kalıpları. İdiomatic Kotlin uygulamalarıyla TDD metodolojisini takip eder.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Kotest, MockK, coroutine testi, property-based testing ve Kover coverage ile Kotlin test kalıpları. İdiomatic Kotlin uygulamalarıyla TDD metodolojisini takip eder.
origin
ECC
Kotlin Test Kalıpları
Kotest ve MockK ile TDD metodolojisini takip ederek güvenilir, sürdürülebilir testler yazmak için kapsamlı Kotlin test kalıpları.
Ne Zaman Kullanılır
Yeni Kotlin fonksiyonları veya class'lar yazarken
Mevcut Kotlin koduna test coverage eklerken
Property-based testler uygularken
Kotlin projelerinde TDD iş akışını takip ederken
Kod coverage için Kover yapılandırırken
Nasıl Çalışır
Hedef kodu belirle — Test edilecek fonksiyon, class veya modülü bul
Kotest spec yaz — Test scope'una uygun bir spec stili seç (StringSpec, FunSpec, BehaviorSpec)
Bağımlılıkları mock'la — Test edilen birimi izole etmek için MockK kullan
Testleri çalıştır (RED) — Testin beklenen hatayla başarısız olduğunu doğrula
Kodu uygula (GREEN) — Testi geçmek için minimal kod yaz
Refactor — Testleri yeşil tutarken implementasyonu iyileştir
Coverage'ı kontrol et — ./gradlew koverHtmlReport çalıştır ve %80+ coverage'ı doğrula
TDD İş Akışı for Kotlin
RED-GREEN-REFACTOR Döngüsü
RED -> Önce başarısız bir test yaz
GREEN -> Testi geçmek için minimal kod yaz
REFACTOR -> Testleri yeşil tutarken kodu iyileştir
REPEAT -> Sonraki gereksinimle devam et
Kotlin'de Adım Adım TDD
// Adım 1: Interface/signature tanımla// EmailValidator.ktpackage com.example.validator
funvalidateEmail(email: String): Result<String> {
TODO("not implemented")
}
// Adım 2: Başarısız test yaz (RED)// EmailValidatorTest.ktpackage com.example.validator
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.result.shouldBeFailure
import io.kotest.matchers.result.shouldBeSuccess
classEmailValidatorTest : StringSpec({
"valid email returns success" {
validateEmail("user@example.com").shouldBeSuccess("user@example.com")
}
"empty email returns failure" {
validateEmail("").shouldBeFailure()
}
"email without @ returns failure" {
validateEmail("userexample.com").shouldBeFailure()
}
})
// Adım 3: Testleri çalıştır - FAIL doğrula// $ ./gradlew test// EmailValidatorTest > valid email returns success FAILED// kotlin.NotImplementedError: An operation is not implemented// Adım 4: Minimal kodu uygula (GREEN)funvalidateEmail(email: String): Result<String> {
if (email.isBlank()) return Result.failure(IllegalArgumentException("Email cannot be blank"))
if ('@' !in email) return Result.failure(IllegalArgumentException("Email must contain @"))
val regex = Regex("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")
if (!regex.matches(email)) return Result.failure(IllegalArgumentException("Invalid email format"))
return Result.success(email)
}
// Adım 5: Testleri çalıştır - PASS doğrula// $ ./gradlew test// EmailValidatorTest > valid email returns success PASSED// EmailValidatorTest > empty email returns failure PASSED// EmailValidatorTest > email without @ returns failure PASSED// Adım 6: Gerekirse refactor et, testlerin hala geçtiğini doğrula
classUserServiceTest : FunSpec({
val repository = mockk<UserRepository>()
val service = UserService(repository)
test("getUser returns user when found") {
val expected = User(id = "1", name = "Alice")
coEvery { repository.findById("1") } returns expected
val result = service.getUser("1")
result shouldBe expected
}
test("getUser throws when not found") {
coEvery { repository.findById("999") } returns null
shouldThrow<UserNotFoundException> {
service.getUser("999")
}
}
})
BehaviorSpec (BDD Stili)
classOrderServiceTest : BehaviorSpec({
val repository = mockk<OrderRepository>()
val paymentService = mockk<PaymentService>()
val service = OrderService(repository, paymentService)
Given("a valid order request") {
val request = CreateOrderRequest(
userId = "user-1",
items = listOf(OrderItem("product-1", quantity = 2)),
)
When("the order is placed") {
coEvery { paymentService.charge(any()) } returns PaymentResult.Success
coEvery { repository.save(any()) } answers { firstArg() }
val result = service.placeOrder(request)
Then("it should return a confirmed order") {
result.status shouldBe OrderStatus.CONFIRMED
}
Then("it should charge payment") {
coVerify(exactly = 1) { paymentService.charge(any()) }
}
}
When("payment fails") {
coEvery { paymentService.charge(any()) } returns PaymentResult.Declined
Then("it should throw PaymentException") {
shouldThrow<PaymentException> {
service.placeOrder(request)
}
}
}
}
})
Kotest Matcher'lar
Temel Matcher'lar
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.*
import io.kotest.matchers.collections.*
import io.kotest.matchers.nulls.*
// Eşitlik
result shouldBe expected
result shouldNotBe unexpected
// String'ler
name shouldStartWith "Al"
name shouldEndWith "ice"
name shouldContain "lic"
name shouldMatch Regex("[A-Z][a-z]+")
name.shouldBeBlank()
// Koleksiyonlar
list shouldContain "item"
list shouldHaveSize 3
list.shouldBeSorted()
list.shouldContainAll("a", "b", "c")
list.shouldBeEmpty()
// Null'lar
result.shouldNotBeNull()
result.shouldBeNull()
// Tipler
result.shouldBeInstanceOf<User>()
// Sayılar
count shouldBeGreaterThan 0
price shouldBeInRange 1.0..100.0// Exception'lar
shouldThrow<IllegalArgumentException> {
validateAge(-1)
}.message shouldBe "Age must be positive"
shouldNotThrow<Exception> {
validateAge(25)
}
MockK
Temel Mocking
classUserServiceTest : FunSpec({
val repository = mockk<UserRepository>()
val logger = mockk<Logger>(relaxed = true) // Relaxed: varsayılanları döndürürval service = UserService(repository, logger)
beforeTest {
clearMocks(repository, logger)
}
test("findUser delegates to repository") {
val expected = User(id = "1", name = "Alice")
every { repository.findById("1") } returns expected
val result = service.findUser("1")
result shouldBe expected
verify(exactly = 1) { repository.findById("1") }
}
test("findUser returns null for unknown id") {
every { repository.findById(any()) } returns nullval result = service.findUser("unknown")
result.shouldBeNull()
}
})
Coroutine Mocking
classAsyncUserServiceTest : FunSpec({
val repository = mockk<UserRepository>()
val service = UserService(repository)
test("getUser suspending function") {
coEvery { repository.findById("1") } returns User(id = "1", name = "Alice")
val result = service.getUser("1")
result.name shouldBe "Alice"
coVerify { repository.findById("1") }
}
test("getUser with delay") {
coEvery { repository.findById("1") } coAnswers {
delay(100) // Async çalışmayı simüle et
User(id = "1", name = "Alice")
}
val result = service.getUser("1")
result.name shouldBe "Alice"
}
})
Coroutine Testi
Suspend Fonksiyonlar İçin runTest
import kotlinx.coroutines.test.runTest
classCoroutineServiceTest : FunSpec({
test("concurrent fetches complete together") {
runTest {
val service = DataService(testScope = this)
val result = service.fetchAllData()
result.users.shouldNotBeEmpty()
result.products.shouldNotBeEmpty()
}
}
test("timeout after delay") {
runTest {
val service = SlowService()
shouldThrow<TimeoutCancellationException> {
withTimeout(100) {
service.slowOperation() // > 100ms sürer
}
}
}
}
})
Flow Testi
import io.kotest.matchers.collections.shouldContainInOrder
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
classFlowServiceTest : FunSpec({
test("observeUsers emits updates") {
runTest {
val service = UserFlowService()
val emissions = service.observeUsers()
.take(3)
.toList()
emissions shouldHaveSize 3
emissions.last().shouldNotBeEmpty()
}
}
test("searchUsers debounces input") {
runTest {
val service = SearchService()
val queries = MutableSharedFlow<String>()
val results = mutableListOf<List<User>>()
val job = launch {
service.searchUsers(queries).collect { results.add(it) }
}
queries.emit("a")
queries.emit("ab")
queries.emit("abc") // Sadece bu aramayı tetiklemeli
advanceTimeBy(500)
results shouldHaveSize 1
job.cancel()
}
}
})
Property-Based Testing
Kotest Property Testing
import io.kotest.core.spec.style.FunSpec
import io.kotest.property.Arb
import io.kotest.property.arbitrary.*
import io.kotest.property.forAll
import io.kotest.property.checkAll
classPropertyTest : FunSpec({
test("string reverse is involutory") {
forAll<String> { s ->
s.reversed().reversed() == s
}
}
test("list sort is idempotent") {
forAll(Arb.list(Arb.int())) { list ->
list.sorted() == list.sorted().sorted()
}
}
test("serialization roundtrip preserves data") {
checkAll(Arb.bind(Arb.string(1..50), Arb.string(5..100)) { name, email ->
User(name = name, email = "$email@test.com")
}) { user ->
val json = Json.encodeToString(user)
val decoded = Json.decodeFromString<User>(json)
decoded shouldBe user
}
}
})
Kover Coverage
Gradle Yapılandırması
// build.gradle.kts
plugins {
id("org.jetbrains.kotlinx.kover") version "0.9.7"
}
kover {
reports {
total {
html { onCheck = true }
xml { onCheck = true }
}
filters {
excludes {
classes("*.generated.*", "*.config.*")
}
}
verify {
rule {
minBound(80) // %80 coverage'ın altında build başarısız
}
}
}
}
Coverage Komutları
# Testleri coverage ile çalıştır
./gradlew koverHtmlReport
# Coverage eşiklerini doğrula
./gradlew koverVerify
# CI için XML raporu
./gradlew koverXmlReport
# HTML raporunu görüntüle (OS'nize göre komutu kullanın)# macOS: open build/reports/kover/html/index.html# Linux: xdg-open build/reports/kover/html/index.html# Windows: start build/reports/kover/html/index.html
# Tüm testleri çalıştır
./gradlew test# Belirli test class'ını çalıştır
./gradlew test --tests "com.example.UserServiceTest"# Belirli testi çalıştır
./gradlew test --tests "com.example.UserServiceTest.getUser returns user when found"# Verbose çıktı ile çalıştır
./gradlew test --info
# Coverage ile çalıştır
./gradlew koverHtmlReport
# Detekt çalıştır (statik analiz)
./gradlew detekt
# Ktlint çalıştır (formatlama kontrolü)
./gradlew ktlintCheck
# Sürekli test
./gradlew test --continuous
En İyi Uygulamalar
YAPILMASI GEREKENLER:
ÖNCE testleri yaz (TDD)
Proje genelinde Kotest'in spec stillerini tutarlı kullan
Suspend fonksiyonlar için MockK'nın coEvery/coVerify'ını kullan
Coroutine testi için runTest kullan
İmplementasyon değil davranışı test et
Pure fonksiyonlar için property-based testing kullan
Netlik için data class test fixture'ları kullan
YAPILMAMASI GEREKENLER:
Test framework'lerini karıştırma (Kotest seç ve ona sadık kal)
Data class'ları mock'lama (gerçek instance'lar kullan)
Hatırla: Testler dokümantasyondur. Kotlin kodunuzun nasıl kullanılması gerektiğini gösterirler. Testleri okunabilir yapmak için Kotest'in açıklayıcı matcher'larını ve bağımlılıkları temiz mock'lamak için MockK kullanın.