DEV Community

Yuri Peixinho
Yuri Peixinho

Posted on

Cluster 4: #10 testes rápidos e independentes, #13 simplicidade e refatoração contínua

Introdução

Esse cluster é diferente dos outros três: ele não previne um tipo de bug, ele sustenta a capacidade de manter os outros nove princípios ao longo do tempo. A segunda lei de Lehman, a Lei da Complexidade Crescente, diz que a complexidade de um sistema aumenta por padrão a menos que alguém trabalhe ativamente para reduzir-la. Refatoração não é uma tarefa opcional de “polimento”, é a única força que contrabalança uma tendência que existe independente da sua vontade. Mas refatorar só é seguro se você consegue confirmar rapidamente que nada quebrou — e é aí que testes lentos ou dependentes de ordem matam a disciplina inteira: se rodar a suíte demora 20 minutos ou falha de forma inconsistente, o time para de rodar, para de confiar, e para de refatorar, e a lei da complexidade crescente passa a valer sem oposição. Esse não é um risco teórico: o relatório The Developer Coefficient (Stripe, 2018), baseado em pesquisa com centenas de desenvolvedores e executivos de engenharia, estimou que uma fração substancial da semana de trabalho — na casa de um terço a 40% — é perdida lidando com dívida técnica e código malfeito, com impacto econômico global estimado em centenas de bilhões de dólares por ano. Esse número é, em essência, o preço agregado de organizações que pararam de fazer #10 e #13 e deixaram a lei de Lehman correr sem freio.

#10 Testes Rápidos e Independentes

A base conceitual mais direta aqui é o acrônimo FIRST, documentado por Robert C. Martin em Clean Code (2008) e creditado a Tim Ottinger e Jeff Langr: um teste de unidade deveria ser Fast (rápido), Independent (independente de outros testes), Repeatable (repetível em qualquer ambiente), Self-Validating (retorna verdadeiro/falso, sem exigir inspeção manual de log) e Timely (escrito no momento certo, perto do código que testa). O título desse princípio no artigo pega exatamente as duas primeiras letras — porque são, na prática, as duas que mais diretamente disparam a espiral de morte descrita na introdução: um teste lento drena paciência a cada execução; um teste dependente de ordem drena confiança a cada falha que não devia ter acontecido.

Exemplo — teste lento e acoplado vs. teste rápido e isolado:

// Lento e acoplado — bate no banco de dados real a cada execução
public class RetificacaoServiceTests
{
    [Fact]
    public void DeveRetificarMovimento_QuandoValorDivergente()
    {
        var context = new FiscalDbContext(_connectionStringReal); // conexão real com SQL Server
        var service = new RetificacaoService(context);

        var resultado = service.Retificar(movimentoId: 123);

        Assert.True(resultado.Sucesso);
        // Cada execução: abre conexão de rede real, e depende de que a linha 123
        // já exista no banco com o estado "certo" — herdado de uma execução anterior
    }
}

// Rápido e isolado — dependência trocada por um fake em memória
public class RetificacaoServiceTests
{
    [Fact]
    public void DeveRetificarMovimento_QuandoValorDivergente()
    {
        var repositorioFake = new RepositorioMovimentoFake(
            new Movimento(id: 123, valor: 500m, status: StatusMovimento.Pendente));
        var service = new RetificacaoService(repositorioFake);

        var resultado = service.Retificar(movimentoId: 123);

        Assert.True(resultado.Sucesso);
        // Roda em memória, sem I/O — milissegundos. O estado de entrada é 100%
        // definido dentro do próprio teste, não herdado de nada externo
    }
}
Enter fullscreen mode Exit fullscreen mode

Exemplo — teste dependente de ordem vs. independente:

// Frágil — os dois testes compartilham uma lista estática entre execuções
public class NumeracaoNotaFiscalTests
{
    private static readonly List<int> _numerosGerados = new();

    [Fact]
    public void PrimeiroNumero_DeveSerUm()
    {
        var numero = GerarProximoNumero(_numerosGerados);
        Assert.Equal(1, numero);
        _numerosGerados.Add(numero);
    }

    [Fact]
    public void SegundoNumero_DeveSerDois()
    {
        var numero = GerarProximoNumero(_numerosGerados);
        Assert.Equal(2, numero);
        // Só passa se PrimeiroNumero_DeveSerUm já rodou antes, e nessa ordem.
        // Rodar sozinho, em paralelo, ou com a suíte embaralhada: falha.
    }
}

// Independente — cada teste monta seu próprio estado, do zero
public class NumeracaoNotaFiscalTests
{
    [Fact]
    public void PrimeiroNumero_DeveSerUm()
    {
        var numero = GerarProximoNumero(new List<int>());
        Assert.Equal(1, numero);
    }

    [Fact]
    public void SegundoNumero_DeveSerDois()
    {
        var numero = GerarProximoNumero(new List<int> { 1 }); // pré-condição explícita, não herdada
        Assert.Equal(2, numero);
    }
}
Enter fullscreen mode Exit fullscreen mode

O segundo exemplo é o padrão mais comum de "flakiness" na prática: dois testes que passam perfeitamente na ordem em que foram escritos, e falham de forma aparentemente aleatória assim que uma pipeline de CI paraliza a execução, ou quando algum framework de teste decide randomizar a ordem deliberadamente — justamente pra expor esse tipo de acoplamento escondido antes que ele vire um problema em produção.

#13 Simplicidade e Refatoração Contínua

A base conceitual mais direta vem de Kent Beck, em Extreme Programming Explained (1999): as Quatro Regras do Design Simples, nessa ordem de prioridade — (1) passa em todos os testes, (2) revela a intenção, (3) sem duplicação, (4) menor número de elementos possível. A ordem importa: só faz sentido buscar "menos elementos" depois de garantir que a intenção está clara e que não há duplicação — simplicidade não é sinônimo de "menos código a qualquer custo", é a ausência de tudo que não precisa estar ali.

A ligação com dívida técnica, que a introdução já traz pelo relatório da Stripe, vem de Ward Cunningham, num relato de experiência na OOPSLA de 1992: código que corta caminho pra entregar mais rápido hoje é, literalmente, um empréstimo — você recebe velocidade agora e paga juros (retrabalho, fricção, tempo de entendimento) em cada mudança futura, até que alguém pague o principal de volta via refatoração. Cunningham não estava fazendo uma crítica moral ao atalho — dívida técnica pode ser escolha racional e deliberada —, o ponto dele é que ela tem que ser paga de algum jeito, consciente ou não.

A técnica pra pagar essa dívida com segurança é formalizada por Martin Fowler em Refactoring: Improving the Design of Existing Code (1999): uma sequência de pequenas transformações, cada uma preservando o comportamento observável do sistema, verificada por testes a cada passo — nunca uma reescrita de uma vez só. Robert C. Martin resume o hábito do dia a dia nisso com a Regra do Escoteiro: deixe o código um pouco mais limpo do que você encontrou, toda vez que mexer nele — não precisa de um projeto formal de refatoração, é disciplina contínua aplicada a cada Pull Request.

Exemplo — duplicação que se acumula em silêncio:

// Complexo — a mesma validação de CPF/CNPJ duplicada em dois lugares do sistema
public class ValidadorNotaFiscal
{
    public bool Validar(NotaFiscal nota)
    {
        var doc = nota.Cliente.Documento.Replace(".", "").Replace("-", "").Replace("/", "");
        if (doc.Length == 11) { /* ~20 linhas de validação de CPF */ }
        else if (doc.Length == 14) { /* ~25 linhas de validação de CNPJ */ }
        return true;
    }
}

public class ValidadorTransmissao
{
    public bool PodeTransmitir(ArquivoEFinanceira arquivo)
    {
        var doc = arquivo.Titular.Documento.Replace(".", "").Replace("-", "").Replace("/", "");
        if (doc.Length == 11) { /* a mesma validação de CPF, copiada e colada */ }
        // quem corrigir um bug aqui precisa lembrar de corrigir no outro lugar também
    }
}

// Simples — a regra vive em um único lugar; os dois validadores delegam
public static class ValidadorDocumento
{
    public static bool EhValido(string documento)
    {
        var limpo = Limpar(documento);
        return limpo.Length switch
        {
            11 => ValidarCpf(limpo),
            14 => ValidarCnpj(limpo),
            _ => false
        };
    }

    private static string Limpar(string doc) => doc.Replace(".", "").Replace("-", "").Replace("/", "");
}

public class ValidadorNotaFiscal
{
    public bool Validar(NotaFiscal nota) => ValidadorDocumento.EhValido(nota.Cliente.Documento);
}
Enter fullscreen mode Exit fullscreen mode

Essa é a Regra 3 de Beck (sem duplicação) na prática. O perigo da versão duplicada não é só o volume de código — é que um bug corrigido num lugar continua vivo no outro, esperando quieto até alguém notar a divergência, geralmente meses depois, geralmente em produção.

Top comments (0)