DEV Community

Cover image for Testando das trincheiras: Usando um "clock" fixo
Hugo Marques
Hugo Marques

Posted on

Testando das trincheiras: Usando um "clock" fixo

Outro curtinho sobre testes. Um dos problemas mais comuns que eu vejo é o uso do tempo variável dentro do código. Como assim? Imagine o seguinte exemplo:

@Component public class TaskScheduler { private static final LocalTime START_OF_WORKING_DAY = LocalTime.of(8, 0); private static final LocalTime END_OF_WORKING_DAY = LocalTime.of(22, 0); public void scheduleTask(Task task) { if (shouldSchedule()) { executeTaskNow(task); } } public static boolean shouldSchedule() { // Get the current time in the system default time zone LocalDateTime now = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()); LocalTime currentTime = now.toLocalTime(); // Check if the current time is within the working hours return !currentTime.isBefore(START_OF_WORKING_DAY) && !currentTime.isAfter(END_OF_WORKING_DAY); } } 
Enter fullscreen mode Exit fullscreen mode

Qual o problema com o código acima? Devido ao Instant.now() no meio do seu código, você não consegue testar o seu método! Como sua lógica é não-determinística e depende do tempo, o seu teste vai passar/falhar conforme o horário que o teste é executado.

Como corrigir esse problema?

Uma alternativa bem simples a partir do java 8 é utilizar a classe Clock para injetar sua dependência que controla o tempo.

No nosso exemplo acima, nosso código ficaria:

@Component public class TaskScheduler { private static final LocalTime START_OF_WORKING_DAY = LocalTime.of(8, 0); private static final LocalTime END_OF_WORKING_DAY = LocalTime.of(22, 0); private final Clock clock; @Autowired public TaskScheduler(Clock clock) { this.clock = clock; } public void scheduleTask(Task task) { if (shouldSchedule()) { executeTaskNow(task); } } public static boolean shouldSchedule() { // Get the current time in the current clock LocalDateTime now = LocalDateTime.ofInstant(clock); LocalTime currentTime = now.toLocalTime(); // Check if the current time is within the working hours return !currentTime.isBefore(START_OF_WORKING_DAY) && !currentTime.isAfter(END_OF_WORKING_DAY); } } 
Enter fullscreen mode Exit fullscreen mode

Dessa forma, você consegue escrever os testes passando o Clock com o tempo que você deseja.

 @Test public void testIsNowWithinWorkingHours_withinHours() { // Arrange: set a fixed instant within working hours Instant fixedInstant = LocalDateTime.of(2024, 6, 1, 10, 0) .toInstant(ZoneOffset.UTC); Clock fixedClock = Clock.fixed(fixedInstant, ZoneId.systemDefault()); TaskScheduler t = new TaskScheduler(fixedClock); // Act: call the method with the fixed clock boolean result = t.shouldSchedule(); // Assert: should be within working hours assertTrue(result, "The time should be within working hours"); } 
Enter fullscreen mode Exit fullscreen mode

Dicas interessantes!

1. Eu uso Spring, como eu crio esse Clock pra ser injetado?

Simples, você pode declarar o seu Clock padrão pro sistema em uma classe de @Configuration.

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.time.Clock; @Configuration public class AppConfiguration { @Bean public Clock clock() { // Retorna o relógio do sistema na zona padrão do sistema return Clock.systemDefaultZone(); } } 
Enter fullscreen mode Exit fullscreen mode

E aí na sua classe é só fazer o @Autowired no construtor que nem fizemos no nosso exemplo acima.

2. Eu uso o meu construtor default em 50 locais diferentes, eu vou ter que alterar todos esses locais pra injetar o Clock agora?

Nada jovem padawan! Um truque bacana é fazer um overloaded constructor:

 @Component public class TaskScheduler { private static final LocalTime START_OF_WORKING_DAY = LocalTime.of(8, 0); private static final LocalTime END_OF_WORKING_DAY = LocalTime.of(22, 0); private final Clock clock; // Essa anotação fala pro nosso Spring da massa usar esse construtor @Autowired public TaskScheduler() { this.clock = Clock.systemDefaultZone(); } // Esse construtor aqui a gente pode usar pros testes. public TaskScheduler(Clock clock) { this.clock = clock; } 
Enter fullscreen mode Exit fullscreen mode

E pronto! Com os dois construtores, você mantém a classe funcionando onde ela já existia, além de permitir a escrita de testes automatizados de forma simples.

Sumário

  1. Evite o uso de tempo variável no meio do código.
  2. Use injeção de dependências para adicionar o seu relógio.
  3. Use construtores padrões e sobrecarga no construtor para permitir adicionar os testes com o mínimo de refatoramento.

Espero que vocês estejam curtindo essas dicas rápidas sobre testes.

Em breve, vou escrever também meus aprendizados sobre paralelismo!

Happy coding!

Top comments (0)