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 Curated skill Quality Excellent · 90 v1.0.0

Get

https://deepseekmodel.com/api/download.php?id=affaan-m-ecc-docs-es-skills-kotlin-testing-skill-md&format=skill
Download .skill Standard format with system_prompt and model_config, ready for any agent framework
The actual content of the system_prompt field in the .skill file.
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).
Keywords that activate this skill. Click one to copy it.

This skill does not provide trigger words.

The downloaded .skill package contains the following fields.
Field Description
formatFormat tag (skill/v1)
skill_idUnique skill ID
nameSkill name
versionVersion
descriptionDescription
categoryCategories (array)
trigger_wordsTrigger words
tagsTags
sourceSource
source_urlSource URL (this page)
exported_atExported at (set per download)
system_promptSystem prompt body
model_configModel config: provider / model / temperature / max_tokens / top_p
examplesExamples
install_guideImport guide for Coze / Dify / Claude / custom frameworks
The same skill can be exported in different platform formats.
.skill Standard format with system_prompt and model_config, ready for any agent framework Download
.skillpro Enhanced format with scripts, tools, dependencies and hooks Download
.json Plain JSON export with system_prompt and model parameters only Download
Coze Markdown with frontmatter, for Coze platform import Download
Dify Dify DSL, import directly after creating an app Download

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

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

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

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