---
title: "Kotest com Kotlin: testes expressivos e property-based testing em 2026"
url: "https://kotlin.dev.br/blog/kotest-kotlin-property-based-testing-2026/"
markdown_url: "https://kotlin.dev.br/blog/kotest-kotlin-property-based-testing-2026.MD"
description: "Aprenda Kotest com Kotlin: setup no Gradle, specs, matchers, property-based testing, coroutines, MockK, integração com JUnit 5 e boas práticas reais hoje."
date: "2026-07-24"
author: "Karina Melo"
---

# Kotest com Kotlin: testes expressivos e property-based testing em 2026

Aprenda Kotest com Kotlin: setup no Gradle, specs, matchers, property-based testing, coroutines, MockK, integração com JUnit 5 e boas práticas reais hoje.


**Resposta rápida:** use **Kotest com Kotlin** quando você quer testes mais legíveis, matchers idiomáticos e geração automática de casos com **property-based testing**. O framework roda integrado ao Gradle e à plataforma JUnit, oferece estilos como `StringSpec`, `FunSpec` e `BehaviorSpec` e pode conviver com MockK, Testcontainers e `kotlinx-coroutines-test`. Para uma adoção segura, comece com **Kotest Runner + Assertions**, mantenha um único estilo por módulo e introduza testes baseados em propriedades apenas nas regras com invariantes claros.

Kotest não é apenas uma sintaxe diferente para `@Test`. Seu principal valor aparece quando o domínio possui muitas combinações de entrada: valores monetários, intervalos, parsers, validações, ordenação, serialização e regras que deveriam continuar verdadeiras para centenas de exemplos. Em vez de escrever manualmente cinco casos, você descreve uma propriedade e deixa o framework explorar o espaço de dados.

Este guia mostra o setup, compara os estilos de especificação, cria testes tradicionais e property-based, integra coroutines e MockK e explica quando continuar com JUnit 5 puro.

## O que é Kotest?

[Kotest](https://kotest.io/) é um framework de testes criado para Kotlin. Ele reúne quatro grupos de recursos:

1. **test framework**, com diferentes estilos para organizar casos;
2. **assertions**, com matchers como `shouldBe`, `shouldContain` e `shouldThrow`;
3. **property testing**, com geradores de dados e redução automática de falhas;
4. **integrações**, incluindo extensões para Spring, Testcontainers e outros componentes do ecossistema JVM.

O runner se conecta à plataforma JUnit, portanto IDEs e pipelines que já descobrem testes JUnit normalmente também conseguem executar specs do Kotest. Isso permite uma migração gradual: uma base pode manter classes JUnit 5 existentes e criar novos testes com Kotest sem reescrever tudo de uma vez.

Se sua necessidade imediata é aprender mocks, lifecycle e testes parametrizados convencionais, comece pelo guia de [JUnit 5 e MockK com Kotlin](/blog/kotlin-testes-junit5-mockk-guia/). Kotest entra como uma camada mais Kotlin-first para expressão e geração de casos.

## Kotest ou JUnit 5: qual escolher?

Kotest e JUnit 5 não precisam ser tratados como rivais absolutos. O Kotest usa a infraestrutura da plataforma JUnit em muitos projetos, enquanto oferece uma API própria para escrever as especificações.

| Critério | Kotest | JUnit 5 puro |
|---|---|---|
| Organização | Vários estilos de spec | Classes e métodos com anotações |
| Assertions | Matchers idiomáticos integrados | Assertions básicas ou bibliotecas externas |
| Property-based testing | Módulo oficial e integrado | Exige biblioteca adicional |
| Curva de aprendizado | Maior se cada time adotar um estilo diferente | Familiar para equipes Java/JVM |
| Interoperabilidade Java | Melhor em bases Kotlin-first | Excelente em bases Java e Kotlin |
| Migração | Pode coexistir com JUnit | Já costuma ser o padrão da base |
| Melhor encaixe | Domínio Kotlin, DSLs, invariantes e testes expressivos | Convenção corporativa, testes simples e equipes mistas |

**Escolha Kotest** quando legibilidade Kotlin-first e property testing trazem ganho real. **Fique com JUnit 5** quando o time já possui um padrão estável, a maioria dos testes é simples ou a base é compartilhada com muitas pessoas desenvolvedoras Java. Adicionar um framework sem resolver um problema concreto só aumenta o custo cognitivo.

## Configurando Kotest no Gradle Kotlin DSL

Consulte as versões compatíveis na [documentação oficial de instalação](https://kotest.io/docs/quickstart) e centralize-as no version catalog ou no gerenciamento de dependências do projeto. O setup mínimo inclui o runner para JUnit 5 e o módulo de assertions.

```kotlin
// build.gradle.kts

dependencies {
    testImplementation("io.kotest:kotest-runner-junit5:<versao-atual>")
    testImplementation("io.kotest:kotest-assertions-core:<versao-atual>")
    testImplementation("io.kotest:kotest-property:<versao-atual>")
}

tasks.test {
    useJUnitPlatform()
}
```

Usar `<versao-atual>` aqui é intencional: copie uma versão estável compatível com seu Kotlin, Gradle e JDK, em vez de reproduzir cegamente um número que pode estar desatualizado. Em um projeto real, fixe a versão para tornar a build reproduzível.

Com version catalog, a configuração pode ficar assim:

```toml
# gradle/libs.versions.toml

[versions]
kotest = "<versao-atual>"

[libraries]
kotest-runner = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
kotest-assertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest" }
```

```kotlin
// build.gradle.kts

dependencies {
    testImplementation(libs.kotest.runner)
    testImplementation(libs.kotest.assertions)
    testImplementation(libs.kotest.property)
}
```

Essa organização evita versões divergentes entre módulos. Para revisar plugins, tasks e catálogos, veja o [guia de Gradle com Kotlin](/guias/guia-kotlin-gradle/).

## Primeiro teste com StringSpec

`StringSpec` é um bom ponto de partida porque sua estrutura é direta: cada string representa o nome de um caso.

```kotlin
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe

class CalculadoraFreteTest : StringSpec({

    "frete deve ser grátis para pedidos a partir de 200 reais" {
        calcularFrete(total = 200.0) shouldBe 0.0
    }

    "frete deve custar 19,90 para pedidos abaixo do limite" {
        calcularFrete(total = 199.99) shouldBe 19.90
    }
})

fun calcularFrete(total: Double): Double =
    if (total >= 200.0) 0.0 else 19.90
```

O operador `shouldBe` lê naturalmente e produz mensagens de falha úteis. O Kotest possui matchers para coleções, strings, exceções, datas, tipos e valores numéricos.

```kotlin
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.collections.shouldContainExactly
import io.kotest.matchers.string.shouldStartWith

"deve validar e ordenar os nomes" {
    val nomes = listOf("Caio", "Ana", "Bruna").sorted()

    nomes shouldContainExactly listOf("Ana", "Bruna", "Caio")
    nomes.first() shouldStartWith "A"
}

"deve rejeitar total negativo" {
    shouldThrow<IllegalArgumentException> {
        require(-10.0 >= 0) { "total inválido" }
    }
}
```

## StringSpec, FunSpec ou BehaviorSpec?

Kotest oferece vários estilos. Isso é poderoso, mas pode virar desorganização se cada arquivo usar uma DSL diferente sem motivo.

### StringSpec para testes objetivos

Use quando cada caso cabe em um nome claro e não precisa de muitos níveis de contexto. É simples para services, mappers, validators e funções de domínio.

### FunSpec para uma estrutura familiar

`FunSpec` usa blocos `test` e `context`, ficando próximo de frameworks tradicionais.

```kotlin
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe

class CupomServiceTest : FunSpec({

    context("cupom percentual") {
        test("deve aplicar dez por cento") {
            aplicarDesconto(100.0, 10) shouldBe 90.0
        }

        test("não deve gerar valor negativo") {
            aplicarDesconto(20.0, 200) shouldBe 0.0
        }
    }
})

fun aplicarDesconto(total: Double, percentual: Int): Double =
    (total * (1 - percentual / 100.0)).coerceAtLeast(0.0)
```

### BehaviorSpec para comportamento de negócio

`BehaviorSpec` organiza o teste com `Given`, `When` e `Then`. Ele ajuda quando produto, QA e desenvolvimento conversam usando cenários de negócio.

```kotlin
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe

class LimiteCreditoTest : BehaviorSpec({

    given("um cliente com limite disponível de 1.000 reais") {
        val conta = ContaCredito(limiteDisponivel = 1_000)

        `when`("uma compra de 400 reais é autorizada") {
            val autorizada = conta.comprar(400)

            then("a compra deve ser aprovada") {
                autorizada shouldBe true
            }

            then("o limite restante deve ser 600 reais") {
                conta.limiteDisponivel shouldBe 600
            }
        }
    }
})

class ContaCredito(var limiteDisponivel: Int) {
    fun comprar(valor: Int): Boolean {
        if (valor <= 0 || valor > limiteDisponivel) return false
        limiteDisponivel -= valor
        return true
    }
}
```

A regra mais importante não é escolher o “melhor” estilo. É escolher **um estilo principal por módulo ou contexto**. Uma base com cinco DSLs exige que cada pessoa reaprenda a navegar pelos testes.

## O que é property-based testing?

Em um teste baseado em exemplos, você escolhe entradas específicas:

```kotlin
normalizar("  Kotlin  ") shouldBe "kotlin"
normalizar("KOTLIN") shouldBe "kotlin"
```

Em **property-based testing**, você declara uma regra que deve valer para um conjunto amplo de entradas. O Kotest gera os dados, executa várias iterações e informa um contraexemplo quando encontra falha.

Uma propriedade da normalização pode ser: “normalizar duas vezes produz o mesmo resultado que normalizar uma vez”. Isso é chamado de **idempotência**.

```kotlin
import io.kotest.core.spec.style.StringSpec
import io.kotest.property.Arb
import io.kotest.property.arbitrary.string
import io.kotest.property.checkAll

class NormalizacaoPropertyTest : StringSpec({

    "normalização deve ser idempotente" {
        checkAll(Arb.string()) { texto ->
            val umaVez = normalizar(texto)
            val duasVezes = normalizar(umaVez)

            duasVezes shouldBe umaVez
        }
    }
})

fun normalizar(valor: String): String =
    valor.trim().lowercase()
```

O objetivo não é gerar números aleatórios por diversão. Uma boa propriedade expressa uma verdade do domínio.

## Criando propriedades úteis

Há padrões que ajudam a encontrar propriedades melhores.

### Ida e volta

Serializar e desserializar deveria preservar o valor:

```kotlin
"encode e decode devem preservar o identificador" {
    checkAll(Arb.long(1L..Long.MAX_VALUE)) { id ->
        val codificado = id.toString(36)
        val decodificado = codificado.toLong(36)

        decodificado shouldBe id
    }
}
```

### Limites ou invariantes

Um desconto nunca deveria aumentar o total nem produzir preço negativo:

```kotlin
import io.kotest.property.arbitrary.double
import io.kotest.property.arbitrary.int
import io.kotest.matchers.doubles.shouldBeAtMost
import io.kotest.matchers.doubles.shouldBeAtLeast

"desconto deve manter resultado entre zero e o total" {
    checkAll(
        Arb.double(0.0..1_000_000.0),
        Arb.int(0..100)
    ) { total, percentual ->
        val resultado = aplicarDesconto(total, percentual)

        resultado.shouldBeAtLeast(0.0)
        resultado.shouldBeAtMost(total)
    }
}
```

### Operação inversa

Adicionar e depois remover o mesmo elemento deveria recuperar o estado anterior, desde que a estrutura e a regra permitam isso.

### Relação com uma implementação simples

Uma implementação otimizada pode ser comparada a uma versão lenta, porém obviamente correta, em entradas geradas.

Essas propriedades costumam encontrar casos de borda que exemplos felizes não cobrem: string vazia, espaços Unicode, zero, números muito grandes, listas duplicadas e limites de intervalo.

## Geradores com Arb

`Arb` representa geradores de valores. Além dos tipos básicos, você pode combinar geradores para produzir objetos do domínio.

```kotlin
import io.kotest.property.arbitrary.arbitrary
import io.kotest.property.arbitrary.email
import io.kotest.property.arbitrary.long
import io.kotest.property.arbitrary.stringPattern

data class Usuario(
    val id: Long,
    val nome: String,
    val email: String
)

val usuarioArb = arbitrary {
    Usuario(
        id = Arb.long(1L..1_000_000L).bind(),
        nome = Arb.stringPattern("[A-Z][a-z]{2,12}").bind(),
        email = Arb.email().bind()
    )
}
```

Geradores de domínio devem produzir dados válidos por padrão. Quando o objetivo é testar validação, crie geradores separados para entradas válidas e inválidas. Misturar tudo em um gerador torna a intenção difícil de entender.

```kotlin
import io.kotest.matchers.longs.shouldBeGreaterThan

"usuário gerado deve manter contrato de cadastro" {
    checkAll(usuarioArb) { usuario ->
        usuario.id shouldBeGreaterThan 0L
        usuario.nome.isNotBlank() shouldBe true
        usuario.email.contains("@") shouldBe true
    }
}
```

## Shrinking: como Kotest reduz uma falha

Quando um gerador encontra um erro, mostrar uma entrada gigantesca nem sempre ajuda. **Shrinking** é o processo de reduzir o caso que falhou para um contraexemplo menor.

Imagine que uma função que divide valores falha somente quando o divisor é zero. O teste pode encontrar o erro depois de gerar muitos números, mas o relatório ideal destaca o caso mínimo relevante, como `(1, 0)`, e não uma combinação enorme.

Essa redução é uma das vantagens práticas do property testing sobre loops com `Random`. Um loop manual pode reproduzir mal a falha, não registra seed adequadamente e não tenta simplificar a entrada. Kotest oferece infraestrutura específica para geração, classificação e diagnóstico.

Mesmo assim, preserve o contraexemplo como teste de regressão quando ele representar uma regra importante. O property test protege a classe inteira de entradas; o exemplo documenta o bug que realmente aconteceu.

## Testando coroutines com Kotest

Specs do Kotest aceitam funções suspensas, o que combina naturalmente com `kotlinx-coroutines-test`. Use `runTest` quando precisar controlar tempo virtual, dispatchers e tarefas pendentes.

```kotlin
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest

class PerfilServiceTest : StringSpec({

    "deve carregar o perfil" {
        runTest {
            val gateway = PerfilGatewayFake()
            val service = PerfilService(gateway)

            service.carregar(42) shouldBe Perfil(42, "Karina")
        }
    }
})

data class Perfil(val id: Long, val nome: String)

class PerfilGatewayFake {
    suspend fun buscar(id: Long): Perfil {
        delay(1_000)
        return Perfil(id, "Karina")
    }
}

class PerfilService(private val gateway: PerfilGatewayFake) {
    suspend fun carregar(id: Long): Perfil = gateway.buscar(id)
}
```

Evite `Thread.sleep` em testes assíncronos. Ele aumenta o tempo da suíte e cria flakiness. Para `Flow`, combine o Kotest com as ferramentas de coroutines ou use Turbine; o guia de [testes de Flow e StateFlow com Turbine](/blog/testando-flow-stateflow-turbine-kotlin-2026/) mostra cancelamento, emissões e estado inicial.

## Kotest com MockK

Kotest Assertions e MockK funcionam bem juntos. O primeiro organiza e verifica resultados; o segundo controla colaboradores quando um fake simples não é suficiente.

```kotlin
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk

class PedidoServiceTest : FunSpec({

    test("deve consultar e devolver o pedido") {
        val repository = mockk<PedidoRepository>()
        val esperado = Pedido(id = 10, status = "PAGO")

        coEvery { repository.buscar(10) } returns esperado

        val service = PedidoService(repository)
        val resultado = service.detalhar(10)

        resultado shouldBe esperado
        coVerify(exactly = 1) { repository.buscar(10) }
    }
})

data class Pedido(val id: Long, val status: String)

interface PedidoRepository {
    suspend fun buscar(id: Long): Pedido
}

class PedidoService(private val repository: PedidoRepository) {
    suspend fun detalhar(id: Long): Pedido = repository.buscar(id)
}
```

Não transforme todo colaborador em mock. Fakes são mais legíveis para repositórios em memória e objetos determinísticos. Mocks são úteis para falhas controladas, APIs difíceis de instanciar e verificação de interações relevantes. Testar cada chamada interna deixa a suíte acoplada à implementação.

## Lifecycle e isolamento entre testes

Kotest permite configurar o ciclo de vida das instâncias e usar hooks como `beforeTest`, `afterTest`, `beforeSpec` e `afterSpec`. O ponto crítico é impedir que estado mutável de um caso vaze para outro.

```kotlin
class CarrinhoTest : FunSpec({
    lateinit var carrinho: Carrinho

    beforeTest {
        carrinho = Carrinho()
    }

    test("deve começar vazio") {
        carrinho.totalItens() shouldBe 0
    }

    test("deve contar item adicionado") {
        carrinho.adicionar("Livro")
        carrinho.totalItens() shouldBe 1
    }
})
```

Se a spec compartilha banco, servidor ou container, documente o escopo e limpe o estado lógico. Para PostgreSQL e outras dependências reais, consulte o guia de [Testcontainers com Kotlin](/blog/testcontainers-kotlin-postgresql-testes-integracao-2026/).

## Testes orientados a dados

Nem toda tabela de exemplos precisa virar property test. Quando os casos são poucos, conhecidos e semanticamente importantes, data-driven testing é mais claro.

```kotlin
import io.kotest.datatest.withData

class ClassificadorTest : FunSpec({
    context("classificação por nota") {
        withData(
            nameFn = { "nota ${it.nota} deve resultar em ${it.resultado}" },
            CasoNota(0, "REPROVADO"),
            CasoNota(59, "REPROVADO"),
            CasoNota(60, "APROVADO"),
            CasoNota(100, "APROVADO")
        ) { caso ->
            classificar(caso.nota) shouldBe caso.resultado
        }
    }
})

data class CasoNota(val nota: Int, val resultado: String)

fun classificar(nota: Int): String =
    if (nota >= 60) "APROVADO" else "REPROVADO"
```

Use uma tabela para limites escolhidos pelo negócio. Use property testing para explorar um intervalo amplo e provar invariantes. Os dois estilos se complementam.

## Kotest em Android

Em projetos Android, separe testes locais da JVM e testes instrumentados. Kotest se encaixa melhor nos testes locais de domínio, ViewModel, reducers, mappers e componentes que não dependem diretamente do runtime Android.

Para código que exige dispositivo, emulator, `Context`, recursos ou componentes do framework, verifique o suporte da ferramenta e da versão adotada pelo projeto. Muitas equipes mantêm JUnit/AndroidX Test nos testes instrumentados e usam Kotest nos módulos Kotlin puros. Essa divisão é pragmática e evita forçar uma única DSL em ambientes com runners diferentes.

Uma arquitetura com domínio desacoplado aumenta a quantidade de código testável na JVM. O conteúdo de [modularização Android com Kotlin](/blog/modularizacao-android-kotlin-compose-2026/) mostra como separar responsabilidades e reduzir dependências de framework.

## Erros comuns ao adotar Kotest

### Usar todos os estilos ao mesmo tempo

A variedade parece atraente, mas prejudica a navegação. Defina um estilo padrão e aceite exceções apenas quando a estrutura do teste realmente pedir.

### Escrever property tests sem uma propriedade

Gerar dados e repetir a mesma assertion de um exemplo não produz automaticamente um bom teste. Nomeie a regra: idempotência, limite, ida e volta, ordenação, equivalência ou preservação.

### Gerar dados inválidos sem intenção

Se o domínio exige identificadores positivos, um gerador genérico de `Long` vai gastar iterações em entradas irrelevantes. Modele geradores válidos e crie cenários inválidos separadamente.

### Ignorar reprodutibilidade

Quando uma falha baseada em geração aparecer, registre seed, contraexemplo e versão das dependências no diagnóstico. Não tente “resolver” diminuindo silenciosamente o número de iterações.

### Misturar teste unitário e integração

Uma spec que sobe Spring, banco e fila não é unitária só porque usa uma DSL concisa. Separe tarefas, tags ou módulos para manter feedback rápido.

### Testar detalhes internos

Assertions elegantes não justificam acoplamento. Teste comportamento observável, não a sequência completa de métodos privados e chamadas internas.

## Estratégia de adoção para um time

Uma migração de baixo risco pode seguir esta ordem:

1. adicione Kotest Runner e Assertions em um módulo Kotlin;
2. escolha `FunSpec` ou `StringSpec` como padrão;
3. converta apenas testes novos ou arquivos que já precisam de manutenção;
4. crie matchers de domínio para assertions repetidas;
5. introduza property testing em parsers, cálculos e validações;
6. monitore tempo de execução e legibilidade nas revisões;
7. documente lifecycle, paralelismo e convenções no repositório;
8. mantenha JUnit 5 onde a migração não traz retorno.

O objetivo não é aumentar a porcentagem de arquivos “em Kotest”. O objetivo é tornar defeitos mais fáceis de encontrar e testes mais fáceis de manter.

## Checklist de um bom setup

- [ ] versões centralizadas e compatíveis com Kotlin, Gradle e JDK;
- [ ] `useJUnitPlatform()` habilitado;
- [ ] um estilo de spec principal por módulo;
- [ ] estado isolado entre casos;
- [ ] coroutines testadas com ferramentas de tempo virtual;
- [ ] mocks usados apenas quando agregam valor;
- [ ] geradores representam o domínio;
- [ ] properties possuem nomes e invariantes claros;
- [ ] contraexemplos importantes viram regressões explícitas;
- [ ] testes unitários e de integração rodam em tarefas identificáveis;
- [ ] a suíte funciona igual localmente e no CI.

## Perguntas frequentes

### Kotest substitui JUnit 5?

Não necessariamente. Em muitos projetos, Kotest usa a plataforma JUnit para descoberta e execução. Você pode manter testes JUnit 5 e adicionar specs Kotest gradualmente. A decisão deve considerar legibilidade, convenção do time e necessidade de property testing.

### Kotest funciona com MockK?

Sim. Use MockK para criar mocks e controlar chamadas suspensas com `coEvery` e `coVerify`, enquanto Kotest organiza a spec e fornece matchers. Também é possível combinar Kotest com fakes, Testcontainers e ferramentas de teste de coroutines.

### Property-based testing substitui testes de exemplo?

Não. Property tests exploram muitas entradas e verificam invariantes; testes de exemplo documentam casos de negócio específicos. Uma suíte forte usa ambos: propriedades para cobertura ampla e exemplos para cenários importantes e regressões conhecidas.

### Qual estilo do Kotest é melhor?

`StringSpec` é simples para casos objetivos, `FunSpec` oferece uma estrutura familiar e `BehaviorSpec` funciona bem para cenários de negócio. A consistência do módulo importa mais que a escolha universal de uma DSL.

### Kotest é indicado para Android?

É especialmente útil em testes locais da JVM e módulos Kotlin puros. Para testes instrumentados, valide o runner e as integrações adotadas pela versão do projeto. Manter Kotest no domínio e AndroidX Test na instrumentação pode ser uma divisão saudável.

## Conclusão

Kotest torna testes Kotlin mais expressivos sem exigir uma migração radical. O ganho inicial vem de specs legíveis e assertions idiomáticas; o diferencial mais profundo aparece no **property-based testing**, que transforma invariantes do domínio em verificações executadas contra muitas entradas.

Comece pequeno: configure runner e assertions, escolha um estilo e converta uma regra importante. Depois, identifique funções com propriedades claras — normalização idempotente, serialização reversível, valores dentro de limites ou equivalência entre implementações — e adicione geradores focados no domínio.

Se o time já possui uma boa suíte JUnit 5, preserve o que funciona. Kotest vale a pena quando reduz ruído, melhora o diagnóstico ou encontra combinações que testes manuais deixariam passar. Para montar a estratégia completa, conecte este guia ao conteúdo de [testes em Kotlin](/guias/guia-testes-kotlin/), aos [testes com JUnit 5 e MockK](/blog/kotlin-testes-junit5-mockk-guia/) e aos [testes de integração com Testcontainers](/blog/testcontainers-kotlin-postgresql-testes-integracao-2026/).
