Java 时间相关单元测试

Java 时间相关单元测试,java,junit,Java,Junit,我需要测试一个函数,其结果将取决于当前时间(使用Joda time的isBeforeNow(),情况就是这样) 是否可以使用(例如,使用Mockito)存根/模拟系统时间,以便我能够可靠地测试功能?使代码可测试的最佳方法(IMO)是将“what's current time”的依赖项提取到自己的接口中,并使用使用当前系统时间(通常使用)的实现还有一个实现,它可以让你设置时间,根据你的需要提前 我在各种情况下使用过这种方法,效果很好。设置很容易-只需创建一个界面(例如,时钟),该界面有一个方法,可

我需要测试一个函数,其结果将取决于当前时间(使用Joda time的
isBeforeNow()
,情况就是这样)

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

使代码可测试的最佳方法(IMO)是将“what's current time”的依赖项提取到自己的接口中,并使用使用当前系统时间(通常使用)的实现还有一个实现,它可以让你设置时间,根据你的需要提前


我在各种情况下使用过这种方法,效果很好。设置很容易-只需创建一个界面(例如,
时钟
),该界面有一个方法,可以以您想要的任何格式为您提供当前时刻(例如,使用Joda Time,或者可能使用
日期
)。

Joda Time支持设置“假”通过
DateTimeUtils
类的
setCurrentMillisFixed
setcurrentmillisofset
方法的当前时间


参见

我使用了一种类似于Jon的方法,但我通常创建一个特殊的测试接口(比如,
模拟工厂
),而不是只为当前时间创建一个专门的接口(比如,
时钟)。我将测试代码所需的所有方法都放在那里。例如,在我的一个项目中,我有四种方法:

  • 返回模拟数据库客户端的程序
  • 创建一个模型通知器对象,该对象将数据库中的更改通知给代码
  • 创建一个模拟java.util.Timer,在我需要时运行任务
  • 返回当前时间的程序
被测试的类有一个构造函数,该构造函数在其他参数中接受此接口。没有这个参数的只会创建这个接口的一个默认实例,它在“现实生活”中工作。接口和构造函数都是包私有的,因此测试API不会泄漏到包之外

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

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


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

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

要补充的是,Joda Time已经包含了一个当前时间界面:

例如:

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));
    }
}
在单元测试中模拟时间(使用,但您可以实现自己的类MillisProviderMock):

使用当前生产时间(在2.9.3中添加到Joda时间):


Joda time内置了对该抽象的支持(见我的答案),因此您不必在代码中引入它。@Laurent:我认为这实际上没有那么优雅。从根本上说,我认为诸如“获取当前时间”之类的服务是一种依赖关系(就像我将随机数生成视为一种依赖关系一样),因此我认为最好将其明确化。这意味着您也可以并行化测试,等等。别误会我的意思:我通常喜欢抽象。然而,在现实项目中,当前时间的概念可能很难抽象,特别是当使用的第三方库不抽象这个概念时。@Laurent:哦,是的,当您使用第三方库时,它很棘手。。。但是,如果您的第三方库碰巧使用了Joda Time,则setCurrentMillisFixed也会有同样的问题:(@Jon:我完全同意你的看法。现在Java 8有了抽象类时钟。但是这些是静态方法-你会以这种方式在单元测试之间引入依赖关系。因此,我更喜欢Jon Skeets解决方案。hstoerr:我不认为测试之间会有依赖关系,除非它们是在不同的线程中执行的(这里可能不是这样),但即便如此,Joda Time还是提供了
DateTimeUtils.setCurrentMillisProvider(DateTimeUtils.MillisProvider)
method,它肯定会允许线程绑定的实现。这听起来像是您要创建一个新接口来封装每个被测类的依赖关系?这将至少需要一个实现。因此,现在对于每个被测类,您都有一个类,至少一个测试类,依赖关系分组inteFace,以及该接口的一个实现?我真的不喜欢这样。我想在代码和它的实际依赖关系之间添加额外的间接层次(即,在查看类之后,我还必须查看依赖关系接口)实际上使代码更难理解。@Dathan,不,不是每个类。只有那些具有必须在测试期间模拟的依赖项的类。在我的应用程序中,恰好只有一个这样的类。而且,类的数量并不意味着什么。如果实现只是
class DefaultMockupFactory,则实现MockupFactory{Timer createTimer(){return new Timer();}}
这并没有那么复杂,是吗?并且拥有
factory.createTimer()
代码中的某些地方也不会使代码更难理解。但我同意,在某些情况下,这可能不是最好的方法。不,我不认为它增加了太多的复杂性——只是不必要的复杂性。我觉得通过使用此facade接口注入的额外间接级别可能会抑制可读性例如,如果我有你的代码样本
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));
    }
}
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);
Check check = new Check(DateTimeUtils.SYSTEM_MILLIS_PROVIDER, someDate);