Testes de Mutação em Kotlin: como escrever código que não teme mudanças

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que a cobertura de código é uma armadilha e os testes de mutação são a salvação

\n\n

Imagine a situação: seu projeto em Kotlin tem 95% de cobertura de testes. O CI está verde, o gerente está feliz, o código vai para produção. E uma semana depois — um bug em produção. Familiar? Muito provavelmente, seus testes apenas são executados, mas não verificam a lógica. É aqui que entram em cena os testes de mutação (mutation testing) — uma metodologia que não permite que os testes "preguiçem".

\n\n

Neste artigo, vamos explorar o que são testes de mutação em Kotlin, como funcionam e como implementá-los em seu projeto usando a ferramenta popular Pitest. Você descobrirá por que 100% de cobertura de código não é garantia de qualidade e como os testes de mutação ajudam a escrever testes verdadeiramente confiáveis.

\n\n

O que são testes de mutação? Explicando com gatos

\n\n

Suponha que você escreveu uma função que determina se um gato pode passar por uma porta:

\n\n
fun canCatFitThroughDoor(catWidth: Int, doorWidth: Int): Boolean {\n    return catWidth <= doorWidth\n}
\n\n

Você escreveu um teste:

\n\n
@Test\nfun `gato de largura 30 passa pela porta 40`() {\n    assertTrue(canCatFitThroughDoor(30, 40))\n}
\n\n

Cobertura — 100%. Mas e se alguém acidentalmente mudar <= para <? O teste ainda passará! Os testes de mutação introduzem automaticamente essas "pequenas quebras" (mutações) no código e verificam se pelo menos um teste falha. Se o teste não falhar, seus testes não estão detectando esse erro. Isso é o que chamamos de testes de mutação em Kotlin.

\n\n

Como funcionam os testes de mutação: de operadores a mutantes "sobreviventes"

\n\n

A ferramenta (por exemplo, Pitest para Kotlin) cria mutantes — versões do seu código com uma única alteração. Aqui estão as mutações típicas:

\n\n
    \n
  • Alteração de operadores de comparação: > → >=, == → !=
  • \n
  • Inversão de condições: if (a > b) → if (a <= b)
  • \n
  • Remoção de chamadas de métodos: someMethod() → vazio
  • \n
  • Substituição de valores de retorno: true → false
  • \n
  • Alteração de números: 10 → 11 ou 0
  • \n
\n\n

Após criar os mutantes, todos os seus testes são executados. Os resultados são divididos em três categorias:

\n\n
    \n
  • Mutantes mortos (Killed) — os testes falharam, a mutação foi detectada. Ótimo!
  • \n
  • Mutantes sobreviventes (Survived) — os testes passaram, embora o código tenha sido alterado. Um sinal de alerta.
  • \n
  • Cobertos, mas não mortos (Covered but not killed) — os testes foram executados, mas não perceberam a alteração.
  • \n
\n\n

Seu objetivo é ter o menor número possível de mutantes sobreviventes (idealmente 0). Isso é o que chamamos de testes de mutação de código Kotlin em ação.

\n\n

Pitest: a principal ferramenta para Kotlin

\n\n

Para linguagens JVM (Java, Kotlin), o padrão de facto é o Pitest (PIT Mutation Testing). Ele suporta Kotlin através do plugin pitest-kotlin. Veja como configurá-lo no Gradle:

\n\n
plugins {\n    id("info.solidsoft.pitest") version "1.15.0"\n}\n\npitest {\n    targetClasses.set(listOf("com.myapp.*")) // quais classes mutar\n    targetTests.set(listOf("com.myapp.*"))    // quais testes executar\n    threads.set(4)                            // threads paralelas\n    outputFormats.set(listOf("XML", "HTML"))  // relatórios\n    mutationThreshold.set(80)                 // percentual mínimo de mortos\n    timeoutConstInMillis.set(10000)           // timeout por teste\n}\n\ndependencies {\n    testImplementation("org.pitest:pitest-kotlin:1.15.0")\n}
\n\n

Execução com um único comando:

\n\n
./gradlew pitest
\n\n

Após a execução, em build/reports/pites

Blogs

Book Recommendations