时间依赖单元测试

时间:2011-04-11 13:42:32

标签: java junit

我需要测试一个函数,其结果将取决于当前时间(使用Joda时间的isBeforeNow(),它会发生)。

    public boolean isAvailable() {
    return (this.someDate.isBeforeNow());
}

是否可以使用(例如,使用Mockito)来存根/模拟系统时间,以便我可以可靠地测试函数?

5 个答案:

答案 0 :(得分:123)

使代码可测试的最佳方法(IMO)是将“当前时间是什么”的依赖关系提取到自己的接口中,使用当前系统时间(正常使用)的实现以及实现它的实现设定时间,根据需要推进等等。

我在各种情况下都使用过这种方法,而且效果很好。它很容易设置 - 只需创建一个接口(例如Clock),它有一个方法可以为您提供您想要的任何格式的当前瞬间(例如使用Joda Time,或者可能是Date)。

答案 1 :(得分:61)

Joda时间支持通过setCurrentMillisFixed类的setCurrentMillisOffsetDateTimeUtils方法设置“假”当前时间。

请参阅https://www.joda.org/joda-time/apidocs/org/joda/time/DateTimeUtils.html

答案 2 :(得分:18)

Java 8引入了抽象类java.time.Clock,它允许您使用替代实现进行测试。这正是Jon在当时的回答中所建议的。

答案 3 :(得分:4)

要添加到Jon Skeet's answer,Joda Time已包含当前时间界面:DateTimeUtils.MillisProvider

例如:

import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils.MillisProvider;

public class Check {
    private final MillisProvider millisProvider;
    private final DateTime someDate;

    public Check(MillisProvider millisProvider, DateTime someDate) {
        this.millisProvider = millisProvider;
        this.someDate = someDate;
    }

    public boolean isAvailable() {
        long now = millisProvider.getMillis();
        return (someDate.isBefore(now));
    }
}

在单元测试中模拟时间(使用Mockito,但您可以实现自己的类MillisProviderMock):

DateTime fakeNow = new DateTime(2016, DateTimeConstants.MARCH, 28, 9, 10);
MillisProvider mockMillisProvider = mock(MillisProvider.class);
when(mockMillisProvider.getMillis()).thenReturn(fakeNow.getMillis());

Check check = new Check(mockMillisProvider, someDate);

使用生产中的当前时间(DateTimeUtils.SYSTEM_MILLIS_PROVIDER已添加到2.9.3中的Joda Time):

Check check = new Check(DateTimeUtils.SYSTEM_MILLIS_PROVIDER, someDate);

答案 4 :(得分:1)

我使用类似于Jon的方法,但我不是仅为当前时间创建一个专门的界面(比如Clock),而是创建一个特殊的测试界面(比如MockupFactory)。我把测试代码所需的所有方法都放在那里。例如,在我的一个项目中,我有四种方法:

  • 返回模拟数据库客户端的文件;
  • 创建一个模拟通知程序对象,通知代码有关数据库中的更改;
  • 创建一个模拟java.util.Timer,在我想要的时候运行任务;
  • 返回当前时间的那个。

正在测试的类有一个构造函数,它接受此接口和其他参数。没有这个参数的那个只是创建了一个“在现实生活中”工作的接口的默认实例。接口和构造函数都是包私有的,因此测试API不会泄漏到包外。

如果我需要更多模仿对象,我只需在该接口中添加一个方法,并在测试和实际实现中实现它。

通过这种方式,我首先设计了适合测试的代码,而不会对代码本身施加太多限制。实际上,由于许多工厂代码集中在一个地方,因此代码变得更加清晰。例如,如果我需要在实际代码中切换到另一个数据库客户端实现,我只需要修改一行而不是搜索构造函数的引用。

当然,就像使用Jon的方法一样,它不适用于您无法或不允许修改的第三方代码。