时间是一件棘手的事情,它总是在变化。 例如,当进行测试时,将这样的移动部分放入代码库中可能会很烦人。 在本文中,我们将看到如何在Java中控制时间。
让我们以一个简单的披萨外送服务示例为例。 它的政策是比萨饼应在10分钟内送达。 当客户要求交货时,系统会根据此策略自动计算交货时间。
public class DeliveryPolicy {
public Delivery createDelivery(Pizza pizza) {
LocalDateTime deliveryTime = LocalDateTime.now().plus(10, ChronoUnit.MINUTES);
return new Delivery(pizza, deliveryTime);
}
}
How Do we Test it?
由于时间总是在变化,因此我们无法在不更改其代码的情况下非常精确地测试此方法。 我们可以测试一下交货时间近似地在将来的10分钟内,但像所有模糊测试一样,它可能会不时失败,具体取决于执行它的上下文。
Make Dependency Visible
该代码之所以难以测试的原因是它具有隐藏的依赖性:时钟。 自Java 8起,现在Date API的方法以时钟为参数。 然后,我们可以使此依赖性可见。
public class DeliveryPolicy {
private final Clock clock;
public DeliveryPolicy() {
this.clock = Clock.systemDefaultZone();
}
public Delivery createDelivery(Pizza pizza) {
LocalDateTime deliveryTime = LocalDateTime.now(clock).plus(10, ChronoUnit.MINUTES);
return new Delivery(pizza, deliveryTime);
}
}
Dependency Injection to the Rescue
现在已经有了依赖项,我们可以像其他任何依赖项一样将其注入到构造函数中,并且测试此方法变得很简单。
public class DeliveryPolicy {
private final Clock clock;
public DeliveryPolicy(ClockProvider clockProvider) {
this.clock = clockProvider.get();
}
public Delivery createDelivery(Pizza pizza) {
LocalDateTime deliveryTime = LocalDateTime.now(clock).plus(10, ChronoUnit.MINUTES);
return new Delivery(pizza, deliveryTime);
}
}
public class DeliveryPolicyTest {
@Test
public void should_schedule_delivery_ten_minutes_later() {
ZonedDateTime now = ZonedDateTime.of(LocalDateTime.of(2017, 7, 18, 0, 0, 0), ZoneId.of("+01"));
DeliveryPolicy policy = new DeliveryPolicy(() -> Clock.fixed(now.toInstant(), now.getZone()));
Delivery delivery = policy.createDelivery(new Pizza());
LocalDateTime tenMinutesLater = LocalDateTime.of(2017, 7, 18, 0, 10, 0);
assertThat(delivery.getDeliveryTime()).isEqualTo(tenMinutesLater);
}
}
Usage with Spring Framework and Spring Boot
如果您在应用程序中使用Spring Framework,则可以创建一个ClockProvider会提供默认Clock的bean。 此外,如果您使用Spring Boot,它可以让您在集成测试中使用@MockBean注解。
@RunWith(SpringRunner.class)
@SpringBootTest
public class DeliveryPolicyIT {
@MockBean
private ClockProvider clockProvider;
@Autowired
private DeliveryPolicy policy;
@Test
public void should_schedule_delivery_ten_minutes_later() {
ZonedDateTime now = ZonedDateTime.of(LocalDateTime.of(2017, 7, 18, 0, 0, 0), ZoneId.of("+01"));
Mockito.when(clockProvider.get()).thenReturn(Clock.fixed(now.toInstant(), now.getZone()));
Delivery delivery = policy.createDelivery(new Pizza());
LocalDateTime tenMinutesLater = LocalDateTime.of(2017, 7, 18, 0, 10, 0);
assertThat(delivery.getDeliveryTime()).isEqualTo(tenMinutesLater);
}
}
@Configuration
public class ClockConfig {
@Bean
public ClockProvider clockProvider() {
return () -> Clock.systemDefaultZone();
}
}
Conclusion
我曾经认为时间像随机一样,是很难考验的。 使用Java 8 Date API,它变得微不足道。 您只需要确认时钟上的依赖关系,并将其像其他依赖关系一样对待即可。 现在,我几乎不使用Date API的方法而不传递时钟作为参数。 这使我可以非常轻松地控制整个应用程序中的时间(在单元测试,集成测试或功能测试中)。