Skills Plugins MCP Prompt Model 博客 我的中心

kotlin-testing

Patrones de pruebas Kotlin con Kotest, MockK, pruebas de coroutines, pruebas basadas en propiedades y cobertura con Kover. Sigue la metodología TDD con prácticas idiomáticas de Kotlin.

DeepseekModel 官方收录技能 质量 优秀 · 90 v1.0.0

获取

https://deepseekmodel.com/api/download.php?id=affaan-m-ecc-docs-es-skills-kotlin-testing-skill-md&format=skill
下载 .skill 标准格式,含 system_prompt 与 model_config,导入任意 Agent 框架即可使用
.skill 文件中 system_prompt 字段的实际内容。
name kotlin-testing description Patrones de pruebas Kotlin con Kotest, MockK, pruebas de coroutines, pruebas basadas en propiedades y cobertura con Kover. Sigue la metodología TDD con prácticas idiomáticas de Kotlin. origin ECC Patrones de Pruebas Kotlin Patrones completos de pruebas Kotlin para escribir pruebas confiables y mantenibles siguiendo la metodología TDD con Kotest y MockK. Cuándo Usar Escribir nuevas funciones o clases Kotlin Agregar cobertura de pruebas a código Kotlin existente Implementar pruebas basadas en propiedades Seguir el flujo de trabajo TDD en proyectos Kotlin Configurar Kover para cobertura de código Cómo Funciona Identificar el código objetivo — Encontrar la función, clase o módulo a probar Escribir un spec Kotest — Elegir un estilo de spec (StringSpec, FunSpec, BehaviorSpec) acorde al alcance de la prueba Mockear dependencias — Usar MockK para aislar la unidad bajo prueba Ejecutar pruebas (ROJO) — Verificar que la prueba falla con el error esperado Implementar código (VERDE) — Escribir el código mínimo para pasar la prueba Refactorizar — Mejorar la implementación manteniendo las pruebas en verde Verificar cobertura — Ejecutar ./gradlew koverHtmlReport y verificar 80%+ de cobertura Ejemplos Las siguientes secciones contienen ejemplos detallados y ejecutables para cada patrón de prueba: Referencia Rápida Specs Kotest — Ejemplos de StringSpec, FunSpec, BehaviorSpec, DescribeSpec en Estilos de Spec Kotest Mocking — Configuración de MockK, mocking de coroutines, captura de argumentos en MockK Flujo de trabajo TDD — Ciclo RED/GREEN/REFACTOR completo con EmailValidator en Flujo de Trabajo TDD para Kotlin Cobertura — Configuración de Kover y comandos en Cobertura con Kover Pruebas Ktor — Configuración de testApplication en Pruebas con Ktor testApplication Flujo de Trabajo TDD para Kotlin El Ciclo ROJO-VERDE-REFACTORIZAR ROJO -> Escribir primero una prueba fallida VERDE -> Escribir el código mínimo para pasar la prueba REFACTORIZAR -> Mejorar el código manteniendo las pruebas en verde REPETIR -> Continuar con el siguiente requisito TDD Paso a Paso en Kotlin // Paso 1: Definir la interfaz/firma // EmailValidator.kt package com.example.validator fun validateEmail (email: String ) : Result<String> { TODO( "not implemented" ) } // Paso 2: Escribir la prueba fallida (ROJO) // EmailValidatorTest.kt package com.example.validator import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.result.shouldBeFailure import io.kotest.matchers.result.shouldBeSuccess class EmailValidatorTest : 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() } }) // Paso 3: Ejecutar pruebas - verificar FALLO // $ ./gradlew test // EmailValidatorTest > valid email returns success FAILED // kotlin.NotImplementedError: An operation is not implemented // Paso 4: Implementar el código mínimo (VERDE) fun validateEmail (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) } // Paso 5: Ejecutar pruebas - verificar PASE // $ ./gradlew test // EmailValidatorTest > valid email returns success PASSED // EmailValidatorTest > empty email returns failure PASSED // EmailValidatorTest > email without @ returns failure PASSED // Paso 6: Refactorizar si es necesario, verificar que las pruebas siguen pasando Estilos de Spec Kotest StringSpec (El Más Simple) class CalculatorTest : StringSpec ({ "add two positive numbers" { Calculator.add( 2 , 3 ) shouldBe 5 } "add negative numbers" { Calculator.add(- 1 , - 2 ) shouldBe - 3 } "add zero" { Calculator.add( 0 , 5 ) shouldBe 5 } }) FunSpec (Similar a JUnit) class UserServiceTest : 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 (Estilo BDD) class OrderServiceTest : 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) } } } } }) DescribeSpec (Estilo RSpec) class UserValidatorTest : DescribeSpec ({ describe( "validateUser" ) { val validator = UserValidator() context( "with valid input" ) { it( "accepts a normal user" ) { val user = CreateUserRequest( "Alice" , "alice@example.com" ) validator.validate(user).shouldBeValid() } } context( "with invalid name" ) { it( "rejects blank name" ) { val user = CreateUserRequest( "" , "alice@example.com" ) validator.validate(user).shouldBeInvalid() } it( "rejects name exceeding max length" ) { val user = CreateUserRequest( "A" .repeat( 256 ), "alice@example.com" ) validator.validate(user).shouldBeInvalid() } } } }) Matchers de Kotest Matchers Principales 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.* // Igualdad result shouldBe expected result shouldNotBe unexpected // Strings name shouldStartWith "Al" name shouldEndWith "ice" name shouldContain "lic" name shouldMatch Regex( "[A-Z][a-z]+" ) name.shouldBeBlank() // Colecciones list shouldContain "item" list shouldHaveSize 3 list.shouldBeSorted() list.shouldContainAll( "a" , "b" , "c" ) list.shouldBeEmpty() // Nulls result.shouldNotBeNull() result.shouldBeNull() // Tipos result.shouldBeInstanceOf<User>() // Números count shouldBeGreaterThan 0 price shouldBeInRange 1.0 . .100 .0 // Excepciones shouldThrow<IllegalArgumentException> { validateAge(- 1 ) }.message shouldBe "Age must be positive" shouldNotThrow<Exception> { validateAge( 25 ) } Matchers Personalizados fun beActiveUser () = object : Matcher<User> { override fun test (value: User ) = MatcherResult( value.isActive && value.lastLogin != null , { "User ${value.id} should be active with a last login" }, { "User ${value.id} should not be active" }, ) } // Uso user should beActiveUser() MockK Mocking Básico class UserServiceTest : FunSpec ({ val repository = mockk<UserRepository>() val logger = mockk<Logger>(relaxed = true ) // Relaxed: retorna valores por defecto val 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 null val result = service.findUser( "unknown" ) result.shouldBeNull() } }) Mocking de Coroutines class AsyncUserServiceTest : 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 ) // Simular trabajo asíncrono User(id = "1" , name = "Alice" ) } val result = service.getUser( "1" ) result.name shouldBe "Alice" } }) Captura de Argumentos test( "save captures the user argument" ) { val slot = slot<User>() coEvery { repository.save(capture(slot)) } returns Unit service.createUser(CreateUserRequest( "Alice" , "alice@example.com" )) slot.captured.name shouldBe "Alice" slot.captured.email shouldBe "alice@example.com" slot.captured.id.shouldNotBeNull() } Spy y Mocking Parcial test( "spy on real object" ) { val realService = UserService(repository) val spy = spyk(realService) every { spy.generateId() } returns "fixed-id" spy.createUser(request) verify { spy.generateId() } // Sobreescrito // Otros métodos usan la implementación real } Pruebas de Coroutines runTest para Funciones Suspend import kotlinx.coroutines.test.runTest class CoroutineServiceTest : 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() // Tarda > 100ms } } } } }) Pruebas de Flows 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 class FlowServiceTest : 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" ) // Solo este debería disparar la búsqueda advanceTimeBy( 500 ) results shouldHaveSize 1 job.cancel() } } }) TestDispatcher import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle class DispatcherTest : FunSpec ({ test( "uses test dispatcher for controlled execution" ) { val dispatcher = StandardTestDispatcher() runTest(dispatcher) { var completed = false launch { delay( 1000 ) completed = true } completed shouldBe false advanceTimeBy( 1000 ) completed shouldBe true } } }) Pruebas Basadas en Propiedades Pruebas de Propiedades con Kotest 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 import kotlinx.serialization.json.Json import kotlinx.serialization.encodeToString import kotlinx.serialization.decodeFromString // Nota: La prueba de roundtrip de serialización requiere que la data class User // esté anotada con @Serializable (de kotlinx.serialization).
Agent 识别该技能的关键词,点击任意一个即可复制。

该技能未提供触发词。

下载的 .skill 包内含以下字段。
字段 说明
format格式标识(skill/v1)
skill_id技能唯一 ID
name技能名称
version版本号
description技能描述
category所属分类(数组)
trigger_words触发词列表
tags标签列表
source来源标识
source_url来源链接(本页地址)
exported_at导出时间(每次下载生成)
system_prompt系统提示词正文
model_config模型参数:provider / model / temperature / max_tokens / top_p
examples示例
install_guide各平台导入说明(Coze / Dify / Claude / 自定义框架)
同一份技能可按不同平台格式导出。
.skill 标准格式,含 system_prompt 与 model_config,导入任意 Agent 框架即可使用 下载
.skillpro 增强格式,额外含脚本 / 工具 / 依赖 / 钩子占位 下载
.json 纯 JSON 导出,只含 system_prompt 与模型参数 下载
Coze 带 frontmatter 的 Markdown,Coze 平台导入用 下载
Dify Dify DSL,创建应用后直接导入 下载

每日精选 Skill 推荐,免费送到你邮箱

输入邮箱,每天接收一个精选 AI Agent 技能推荐。完全免费,持续更新。

验证码 --

提交后我们会发送一封确认邮件,点击邮件里的链接才会开始收信。

完全免费,取消任意时间。我们不会发送垃圾邮件。