Testes e qualidade de código

O que define um bom teste unitário: resumo completo dos 5 princípios FIRST

Você já escreveu um monte de código de teste, mas não conseguiu encontrar os bugs de verdade e viu os testes quebrarem aos montes sempre que corrigia uma funcionalidade?

4 min de leitura
Imagem de capa de O que define um bom teste unitário: resumo completo dos 5 princípios FIRST

Você já escreveu um monte de código de teste, mas não conseguiu encontrar os bugs de verdade e viu os testes quebrarem aos montes sempre que corrigia uma funcionalidade?

Em resumo, bons testes unitários não são avaliados pela quantidade ou pela cobertura, mas por seguirem os princípios FIRST. FIRST significa Fast (rápido), Isolated (isolado), Repeatable (repetível), Self-validating (autovalidável) e Timely (oportuno).

Lembre-se desses cinco pontos, e os testes deixarão de ser um peso para se tornar uma rede de segurança confiável. Hoje, vou explicar cada um deles com exemplos da minha experiência.

Os princípios FIRST em um só olhar

FIRST é um conjunto de princípios popularizado por Robert Martin (Uncle Bob) em seu livro Clean Code.

Cada letra representa uma condição de um bom teste unitário.

Letra Significado Ponto principal
F Fast Centenas em poucos segundos
I Isolated Os testes não interferem entre si
R Repeatable O mesmo resultado sempre e em qualquer lugar
S Self-validating Aprovação ou falha determinada automaticamente
T Timely Escrito no momento certo

Vamos analisar cada um deles.


Fast: por que precisa ser tão rápido?

Testes unitários precisam ser rápidos. De verdade.

Se forem lentos, os desenvolvedores deixam de executá-los. Uma suíte que leva 3 minutos por execução acaba sendo executada apenas antes do commit.

Os testes cumprem seu papel quando você consegue executar centenas deles sem esforço sempre que altera o código.

As causas mais comuns da lentidão são acessos ao DB real, à rede e ao sistema de arquivos. O padrão é substituir essas dependências externas por dublês de teste (Mock, Stub).

Uma boa meta é levar alguns milissegundos por teste e concluir centenas deles em poucos segundos no total.

Você conhece aquela sensação de alívio quando todos os testes ficam verdes?
Você conhece aquela sensação de alívio quando todos os testes ficam verdes?

Isolated: por que os testes não podem interferir uns nos outros

Cada teste deve ser independente. Ele não pode depender do resultado de outro teste.

Por exemplo, se o teste A cria dados usados pelo teste B, B também desmorona no instante em que A falha. O resultado ainda muda quando a ordem de execução muda.

Por isso, cada teste deve preparar os dados que usa e limpá-los corretamente ao terminar.

Este é um padrão comum para inicializar o estado antes de cada teste.

struct CalculatorTests {
    let calculator: Calculator

    init() {
        // Uma nova instância é criada a cada teste → impede o compartilhamento de estado suite 
        calculator = Calculator()
    }
}

Assim, nenhum teste afeta os outros, independentemente de qual seja executado primeiro.

Suíte de testes do Swift Testing escrita com @Test e init() no Xcode, com a tela de resultados aprovados
Graças ao init(), cada teste começa com uma nova instância e permanece independente

Repeatable & Self-validating: os resultados não podem oscilar

Comecemos pelo R, a repetibilidade. O teste deve produzir o mesmo resultado, não importa quantas vezes ou em qual ambiente seja executado.

Um teste que passa hoje, mas falha amanhã, é chamado de teste flaky. Ele é um dos principais responsáveis por destruir a confiança.

Depender do horário atual ou de valores aleatórios facilita esse problema. Se precisar lidar com tempo, é mais seguro controlá-lo injetando um valor fixo.

S, a autovalidação, também é importante. O teste deve determinar sozinho se passou ou falhou.

Imprimir um valor no console e conferi-lo visualmente não é teste. Use asserções como #expect para determinar o resultado automaticamente.

Um bom teste mostra apenas uma de duas coisas: verde ou vermelho. Se faz alguém pensar “será que está certo?”, o teste já falhou.


Timely: quando é melhor escrever os testes?

O T final representa oportunidade: escrever os testes no momento adequado.

No TDD (Test-Driven Development, desenvolvimento orientado a testes), os testes são escritos antes do código de produção. Mesmo que você não siga exatamente essa abordagem, o essencial é escrevê-los perto do momento de implementar a funcionalidade.

Se você espera algumas semanas depois de terminar uma funcionalidade para escrever todos os testes, o código geralmente já se consolidou em uma estrutura difícil de testar.

Não deixar os testes para depois: esse é o ponto central de Timely.


Resumo

FIRST é menos um conjunto de regras para decorar e mais uma lista de verificação que mostra o que saiu do lugar quando os testes começam a parecer frustrantes.

Está lento? Os testes estão interligados? Os resultados oscilam? Exigem verificação manual? Você está escrevendo tarde demais? Só verificar esses cinco pontos já melhora muito a qualidade dos testes.

A partir de hoje, lembre-se do FIRST sempre que escrever um teste. Ele certamente se tornará um colega confiável.

Continue lendo