Pular para o conteúdo

Testabilidade e Arquitetura

Se testar uma classe é difícil, o problema quase nunca é o teste — é o design. Código difícil de testar é código acoplado a detalhes concretos (banco, relógio, rede, framework). O mesmo DIP que faz o domínio depender de abstrações também torna essas dependências substituíveis por dublês no teste. Ou seja: arquitetura e testabilidade são a mesma moeda vista de dois lados.

graph LR
    subgraph Produção
        UC1["Caso de uso"] --> P1["Porta ContaRepository"]
        P1 -.implementada por.-> A1["ContaJpaAdapter"]
    end
    subgraph Teste
        UC2["Caso de uso"] --> P2["Porta ContaRepository"]
        P2 -.implementada por.-> A2["InMemoryContaRepository (Fake)"]
    end

O caso de uso não muda entre produção e teste. Só troca o adapter que satisfaz a porta. Essa é a essência de por que Hexagonal/Clean são tão testáveis.

A taxonomia de dublês (Test Doubles - Stub, Spy, Mock e Fake) mapeia diretamente nos conceitos de arquitetura:

Conceito de teste Conceito de arquitetura
A dependência que você substitui Uma driven port (ex: repositório, gateway)
Fake (ex: InMemoryRepository) Um adapter secundário de teste, feito à mão
Stub (respostas canned) Adapter mínimo que devolve estado fixo
Mock (verifica interação) Verificação de que o núcleo chamou a porta corretamente
O que você testa “por dentro” O núcleo: Entidades, Value Objects, Use Cases

A arquitetura também diz que tipo de teste cabe onde — conectando a pirâmide de testes às camadas:

Elemento Tipo de teste ideal Por quê
Value Objects, Entidades, Agregados Unit puro, sem mocks São lógica pura; invariantes se testam com asserts diretos
Use Cases / serviços de aplicação Unit com Fakes nas portas Orquestração testada rápido, sem infraestrutura
Adapters secundários (JPA, HTTP) Integração (Testcontainers) O que importa é a fidelidade à tecnologia real
Fluxo ponta a ponta E2E (poucos) Confiança de que os anéis se encaixam

Repare que o domínio rico do DDD é o mais barato de testar: lógica sem dependências. Entidades anêmicas empurram essa lógica para services cheios de mocks — um cheiro que Code Smells e SOLID detalha.

Não é coincidência que TDD produza designs parecidos com Hexagonal/Clean: escrever o teste primeiro te obriga a inventar a abstração (a porta) antes do detalhe. O teste é o primeiro cliente do seu núcleo, e ele reclama na hora se você acoplar infraestrutura cedo demais. TDD é, na prática, inversão de dependência guiada por pressão de teste.

  • Mockar o que você possui vs o que não controla: criar mock de um value object ou de uma entidade é sinal de over-mocking. Dublês são para portas (fronteiras), não para lógica pura.
  • Testar o adapter com mock do driver: mockar o EntityManager/JDBC testa a sua imaginação, não a integração. Adapter se testa contra a tecnologia real (Testcontainers).
  • Vazar tipos de framework para dentro do teste de domínio: se o teste da entidade precisa de @SpringBootTest, a regra da dependência foi violada.
  • “Preciso de mock para tudo”: excesso de mocks costuma denunciar acoplamento alto — o design, não o teste, é o problema.
  1. Por que se diz que um Fake é um adapter, e não apenas “código de teste”?
Resposta

Porque ele implementa a mesma porta (interface) que o adapter de produção. Do ponto de vista do núcleo, é indistinguível de qualquer outro adapter secundário — só vive no código de teste e guarda estado em memória.

  1. Qual mecanismo comum explica tanto a testabilidade quanto a arquitetura Hexagonal/Clean?
Resposta

A inversão de dependência (DIP): o núcleo depende de abstrações, então qualquer implementação (real ou dublê) pode ser plugada na porta.

  1. Se testar um serviço exige mockar cinco colaboradores, o que isso provavelmente indica?
Resposta

Acoplamento alto e/ou responsabilidades demais (violação de SRP). O sintoma aparece no teste, mas a causa é de design. Ver Code Smells e SOLID.

  1. Por que entidades e value objects são os elementos mais baratos de testar?
Resposta

São lógica pura, sem dependências de infraestrutura. Testam-se com asserts diretos sobre invariantes, sem mocks nem contexto de framework.