Seus testes estão mentindo, e você nem sabe disso
\n\nImagine a situação: você escreveu 200 testes unitários, a cobertura de código é de 95%, o CI está verde, o gerente está feliz. Você lança a versão, e uma semana depois a produção cai devido a um bug que os testes deveriam ter pego. Familiar? Bem-vindo ao mundo da falsa confiança, onde o mutation testing (teste de mutação) atua como um espelho honesto para o seu código Swift.
\n\nNeste artigo, vamos explorar o que é o teste de mutação em Swift, como ele funciona na prática e por que todo desenvolvedor iOS deveria adicioná-lo ao seu arsenal. Sem teoria chata — apenas código, exemplos e casos reais.
\n\nO que é teste de mutação e por que você precisa dele?
\n\nTeste de mutação (mutation testing) é um método para avaliar a qualidade dos testes através da introdução de pequenas alterações (mutações) no código-fonte. Essas versões modificadas são chamadas de mutantes. Se os testes não detectarem o mutante (ou seja, não falharem), significa que sua cobertura de teste é fraca e esse mutante sobrevive.
\n\nPor que isso é importante? Métricas tradicionais como cobertura de código mostram apenas qual porcentagem do código foi executada durante os testes. Mas elas não respondem à pergunta principal: os testes verificam a correção da lógica? O teste de mutação dá a resposta: se você alterar acidentalmente a condição if a > b para if a < b, seus testes perceberão?
Como funciona o teste de mutação em Swift: de forma simples
\n\nO processo funciona assim:
\n\n- \n
- Código-fonte — seu arquivo Swift de trabalho. \n
- Geração de mutantes — o programa cria automaticamente várias versões do código com um pequeno erro (por exemplo, troca
+por-). \n - Execução dos testes — para cada mutante, o conjunto completo de testes unitários é executado. \n
- Análise dos resultados — se os testes falharem, o mutante está "morto". Se os testes passarem, o mutante "sobreviveu", e isso é uma bandeira vermelha. \n
A porcentagem de mutantes mortos é chamada de mutation score. Quanto maior, mais confiáveis são seus testes.
\n\nExemplo prático: escrevendo código e testes em Swift
\n\nVamos imaginar que estamos escrevendo uma calculadora de descontos simples para uma loja online em Swift. Aqui está nosso código de produção:
\n\n// DiscountCalculator.swift\nstruct DiscountCalculator {\n func calculateDiscount(price: Double, isPremiumUser: Bool) -> Double {\n var discount: Double = 0.0\n \n if price > 100 {\n discount += 0.1 // 10% de desconto básico\n }\n \n if isPremiumUser {\n discount += 0.05 // 5% adicional para premium\n }\n \n return price * (1 - discount)\n }\n}\n\n\nParece tudo lógico. Vamos escrever os testes unitários:
\n\n// DiscountCalculatorTests.swift\nimport XCTest\n\nfinal class DiscountCalculatorTests: XCTestCase {\n let calculator = DiscountCalculator()\n \n func testDiscountForRegularUserLowPrice() {\n let result = calculator.calculateDiscount(price: 50, isPremiumUser: false)\n XCTAssertEqual(result, 50.0, accuracy: 0.01)\n }\n \n func testDiscountForRegularUserHighPrice() {\n let result = calculator.calculateDiscount(price: 200, isPremiumUser: false)\n XCTAssertEqual(result, 180.0, accuracy: 0.01)\n }\n \n func testDiscountForPremiumUserLowPrice() {\n let result = calculator.calculateDiscount(price: 50, isPremiumUser: true)\n XCTAssertEqual(result, 47.5, accuracy: 0.01)\n }\n \n func testDiscountForPremiumUserHighPrice() {\n let result = calculator.calculateDiscount(price: 200, isPremiumUser: true)\n XCTAssertEqual(result, 170.0, accuracy: 0.01)\n }\n}\n\n\nOs testes passam. Cobertura — 100%. Mas vamos ver o que o teste de mutação tem a dizer.
\n\