Como testar com unit tests código que depende do tempo
Testar código que depende da hora atual levanta questões de design mais profundas sobre como o software deve interagir com estado externo. Comentadores comparam abordagens como clocks injetados por dependência, passagem de timestamps para funções puras, monkey-patching de APIs de tempo e shims em nível de sistema como libfaketime, observando que as escolhas variam por linguagem e por se você está escrevendo testes unitários ou de integração. Muitos argumentam que tratar o tempo como uma dependência de primeira classe melhora a testabilidade e a arquitetura, enquanto outros apontam limites difíceis quando bibliotecas de terceiros, características de desempenho ou temporização de hardware real estão envolvidos.
Princípio central: tratar o tempo como uma dependência
- Muitos argumentam que “o tempo é uma dependência como qualquer outra.”
- Padrões recomendados: injetar uma interface de clock/timer, passar uma função
now(), ou passar timestamps explícitos para a lógica. - Ordem de preferência (para alguns):
- Melhor: a lógica recebe dados de tempo como argumento.
- Em seguida: a lógica recebe uma função que produz tempo.
- Depois: injetar um objeto de clock/timer via DI.
Ferramentas específicas de linguagem e framework
- O artigo em C++ foi visto como restrito; outras linguagens oferecem mecanismos mais fáceis.
- Linguagens dinâmicas (JS, Python, Ruby) frequentemente fazem monkey-patch do tempo ou usam bibliotecas: Jest fake timers, freezegun/time-machine (Python), Timecop/Rails
travel_to(Ruby). - Java normalmente injeta
Clock. .NET 8 adicionaTimeProvidereFakeTimeProvider. Código Go frequentemente passatime.Nowcomo parâmetro de função.
Bancos de dados e múltiplos relógios
- Usar
NOW()em SQL não é inerentemente ruim, mas pode introduzir um segundo relógio. - Problemas surgem quando o tempo da aplicação é mockado, mas o tempo do banco é real, ou quando o agendador e o executor usam máquinas/relógios diferentes.
- Muitos sugerem passar o tempo para as queries para manter consistência.
Bibliotecas de terceiros e escopo de teste
- Se código que você não controla chama o tempo real, talvez seja impossível injetar seu próprio clock.
- Respostas sugeridas:
- Usar testes de integração e aceitar verificações mais lentas/menos isoladas.
- Envolver a biblioteca por trás da sua própria interface e fornecer uma implementação de teste.
- Debate sobre se testes de primeira parte deveriam alguma vez testar explicitamente o comportamento de terceiros; há forte discordância aqui.
Intercepção de tempo em nível de sistema
- Ferramentas como libfaketime (e bibliotecas experimentais semelhantes) interceptam chamadas de sistema ou funções vDSO para falsificar o tempo no nível do processo.
- São vistas como poderosas, mas às vezes desajeitadas (variáveis de ambiente, LD_PRELOAD) e exageradas para testes unitários simples.
Testes de integração que dependem do tempo
- Testes ingênuos baseados em
sleep()são lentos e instáveis sob carga. - Solução comum: fazer polling de efeitos colaterais observáveis em vez de esperar durações fixas.
- Outras propostas: faketime no nível do processo, clocks locais por tenant controláveis pelo harness de teste, ou “test clocks” injetados em um nível arquitetural mais alto.
Mocks, complexidade de DI e crítica ao artigo
- Alguns se preocupam que injetar clocks e mocks adiciona dependências e torna o código mais difícil de trabalhar; outros dizem que clocks injetados ajudam muito tanto nos testes quanto no depuramento.
- Mocks são vistos como aceitáveis se testam interfaces públicas e bem definidas, e se não forem usados em excesso.
- Alguns criticam o artigo por ser simplista demais: sistemas reais sensíveis ao tempo (desempenho, hardware, multimídia) têm desafios que vão muito além de fábricas de clock e templates.