Princípio FIRST
Visão geral
Seção intitulada “Visão geral”FIRST não é dogma; é um conjunto de heurísticas que, quando violadas, produzem sintomas previsíveis: suíte lenta, flaky, com testes que dependem uns dos outros e que ninguém confia. Cada letra abaixo traz uma violação e a correção correspondente.
graph LR
F[Fast] --- I[Independent]
I --- R[Repeatable]
R --- S[Self-validating]
S --- T[Timely]
F — Fast
Seção intitulada “F — Fast”Testes precisam ser rápidos porque só são úteis se você os roda o tempo todo. Uma suíte unitária que leva minutos deixa de ser rodada a cada mudança, e o feedback rápido — a razão de existir — evapora.
O que deixa testes lentos: I/O real (banco, rede, disco), Thread.sleep, @SpringBootTest carregando o contexto inteiro para um teste de regra de negócio.
Violação:
@SpringBootTestclass CalculadoraFreteTest {
@Autowired CalculadoraFrete calculadora;
@Test void calculaFretePorRegiao() throws InterruptedException { Thread.sleep(2000); assertThat(calculadora.calcular("SP", 5.0)).isEqualByComparingTo("15.00"); }}Correção — regra pura, sem contexto Spring, sem sleep:
class CalculadoraFreteTest {
CalculadoraFrete calculadora = new CalculadoraFrete();
@Test void calculaFretePorRegiao() { assertThat(calculadora.calcular("SP", 5.0)).isEqualByComparingTo("15.00"); }}Testes que precisam de I/O real existem, mas são de integração, não unitários, e vivem em outra camada da pirâmide (Tipos de Teste - Unit, Integration e E2E).
I — Independent / Isolated
Seção intitulada “I — Independent / Isolated”Cada teste deve montar seu próprio mundo e não depender da ordem de execução nem de estado deixado por outro teste. Testes acoplados por estado compartilhado quebram em cascata e são impossíveis de rodar isoladamente.
Violação — estado estático vazando entre testes:
class ContadorTest {
static Contador contador = new Contador();
@Test void a_incrementa() { contador.incrementar(); assertThat(contador.valor()).isEqualTo(1); }
@Test void b_incrementaDeNovo() { contador.incrementar(); assertThat(contador.valor()).isEqualTo(1); }}b_incrementaDeNovo só passa se a_incrementa rodar antes — e falha se a ordem mudar. Correção — instância nova por teste via @BeforeEach:
class ContadorTest {
Contador contador;
@BeforeEach void setup() { contador = new Contador(); }
@Test void incrementaUmaVez() { contador.incrementar(); assertThat(contador.valor()).isEqualTo(1); }}Isolamento também significa isolar dependências: substituir colaboradores por dublês para que o teste falhe por um único motivo (ver Test Doubles - Stub, Spy, Mock e Fake).
R — Repeatable
Seção intitulada “R — Repeatable”O mesmo teste, rodado quantas vezes for, em qualquer máquina (sua, do colega, do CI), deve dar o mesmo resultado. Dependência de ambiente, fuso, locale, rede ou relógio quebra a repetibilidade.
Violação:
@Testvoid saudacaoDaManha() { var saudacao = new Saudacao(); assertThat(saudacao.gerar()).isEqualTo("Bom dia");}Passa de manhã, falha à tarde. Correção — injetar o Clock e controlar o tempo:
@Testvoid saudacaoDaManha() { Clock manha = Clock.fixed(Instant.parse("2026-07-09T09:00:00Z"), ZoneId.of("America/Sao_Paulo")); var saudacao = new Saudacao(manha);
assertThat(saudacao.gerar()).isEqualTo("Bom dia");}Testes de integração que dependem de banco tornam-se repetíveis com Testcontainers, que sobe uma instância limpa e efêmera a cada execução — não um banco de dev compartilhado.
S — Self-validating
Seção intitulada “S — Self-validating”O teste decide sozinho se passou ou falhou, com um resultado booleano claro. Nada de inspecionar log no console ou comparar arquivos na mão.
Violação:
@Testvoid calculaDesconto() { var pedido = new Pedido(new BigDecimal("200")); System.out.println("Desconto: " + pedido.desconto());}Sempre “passa”, porque nunca afirma nada; exige um humano lendo a saída. Correção — um assert explícito:
@Testvoid calculaDesconto() { var pedido = new Pedido(new BigDecimal("200"));
assertThat(pedido.desconto()).isEqualByComparingTo("20.00");}AssertJ ajuda aqui com mensagens de falha ricas e API fluente, tornando o veredito e a causa imediatamente legíveis.
T — Timely
Seção intitulada “T — Timely”Escreva os testes no momento certo — idealmente antes ou junto ao código de produção, não semanas depois. Testes adiados tendem a nunca ser escritos, e quando são, apenas confirmam o código existente em vez de guiar seu design.
Escrever o teste primeiro (a essência do Test-Driven Development) pressiona o código a ser testável: dependências viram colaborações injetáveis, interfaces ficam enxutas. Escrever depois frequentemente revela que a classe é difícil de testar — sinal de acoplamento que já custou caro para nascer.
O “Timely” é a ponte entre FIRST e TDD: não é só sobre a qualidade do teste, mas sobre o momento que maximiza seu efeito de design.
Anti-padrões e erros comuns
Seção intitulada “Anti-padrões e erros comuns”- Slow suite normalizada: aceitar minutos de espera até ninguém mais rodar a suíte localmente.
- Fixture compartilhada mutável:
staticou campos reaproveitados sem reset, criando acoplamento por ordem. @Testsem assert: passa sempre, valida nada (viola Self-validating).Thread.sleeppara sincronizar: lento e flaky; use Awaitility em cenários assíncronos.- Dependência de locale/timezone/rede: quebra Repeatable fora da sua máquina.
- Testes escritos muito depois: perdem o poder de design e viram só documentação retroativa.
Relações
Seção intitulada “Relações”- Testes e TDD
- Fundamentos da Automação de Testes
- Tipos de Teste - Unit, Integration e E2E
- Test-Driven Development
- Test Doubles - Stub, Spy, Mock e Fake
Perguntas de revisão
Seção intitulada “Perguntas de revisão”1. Por que “Fast” é pré-requisito para o valor de uma suíte unitária?
Resposta
Porque testes só entregam feedback rápido se forem executados com frequência. Uma suíte lenta deixa de ser rodada a cada mudança, e sem execução frequente o benefício central — pegar a regressão segundos depois de introduzi-la — desaparece.
2. Como estado estático compartilhado viola “Independent”?
Resposta
Ele faz um teste depender do resultado de outro, criando acoplamento por ordem de execução. O teste passa numa ordem e falha em outra, e não pode rodar isolado. A correção é recriar o objeto sob teste em @BeforeEach.
3. O que torna um teste não-repetível e como Testcontainers ajuda na integração?
Resposta
Dependência de tempo, locale, rede ou de um ambiente compartilhado. Testcontainers sobe um banco efêmero e limpo por execução, eliminando o estado residual de um banco de dev compartilhado e tornando o teste repetível em qualquer máquina.
4. Por que “Timely” conecta FIRST a TDD?
Resposta
Porque escrever o teste no momento certo (antes/junto ao código) faz o teste guiar o design rumo a algo testável e desacoplado. Adiar o teste o reduz a documentação retroativa e costuma expor acoplamento já custoso — o teste deixa de moldar o código.