我需要测试一个函数,该函数的结果将取决于当前时间(使用Joda time的时间isBeforeNow()
)。
public boolean isAvailable() {
return (this.someDate.isBeforeNow());
}
是否可以使用(例如使用Mockito)存根/模拟系统时间,以便我可以可靠地测试功能?
我需要测试一个函数,该函数的结果将取决于当前时间(使用Joda time的时间isBeforeNow()
)。
public boolean isAvailable() {
return (this.someDate.isBeforeNow());
}
是否可以使用(例如使用Mockito)存根/模拟系统时间,以便我可以可靠地测试功能?
Answers:
Joda time支持通过类的setCurrentMillisFixed
和setCurrentMillisOffset
方法设置“假”当前时间DateTimeUtils
。
参见https://www.joda.org/joda-time/apidocs/org/joda/time/DateTimeUtils.html
DateTimeUtils.setCurrentMillisProvider(DateTimeUtils.MillisProvider)
方法,该方法肯定会允许线程绑定的实现。
使代码可测试的最佳方法(IMO)是将“当前时间是什么”的依赖项提取到其自己的界面中,该实现使用当前系统时间(通常使用),而实现则可以设置时间,根据需要进行升级等。
我在各种情况下都使用了这种方法,并且效果很好。设置很容易-只需创建一个界面(例如Clock
),该界面即可通过一种方法以所需的任意格式(例如,使用Joda Time或可能使用Date
)为您提供当前时刻。
Java 8引入了抽象类java.time.Clock
,该类允许您使用替代实现进行测试。这正是乔恩当时在回答中所暗示的。
Supplier
感兴趣的时间单位(例如LocalDateTime
)。
LocalDateTime.now()
不将Clock
传递为,则无法控制当前返回的时间now()
。
Supplier<LocalDateTime>
,您可以在其中方便(单元测试)的地方插入真实的适配器(例如LocalDateTime::now
-)或伪造的适配器(例如() -> LocalDateTime.of(2017, 10, 24, 0, 0)
)。不需要整体Clock
。
要添加到Jon Skeet的答案中,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);
使用生产中的当前时间(在2.9.3中将DateTimeUtils.SYSTEM_MILLIS_PROVIDER添加到Joda Time中):
Check check = new Check(DateTimeUtils.SYSTEM_MILLIS_PROVIDER, someDate);
我使用的方法类似于Jon的方法,但Clock
我通常不创建一个专门的界面(例如),而是创建一个特殊的测试界面(例如MockupFactory
)。我把测试代码所需的所有方法放在这里。例如,在我的一个项目中,我有四种方法:
被测试的类具有一个构造函数,该构造函数在其他参数中接受此接口。没有这个参数的人只是创建该接口的默认实例,该实例在“现实生活中”起作用。接口和构造函数都是包私有的,因此测试API不会泄漏到包外部。
如果我需要更多的模仿对象,则只需向该接口添加一个方法,然后在测试和实际实现中都将其实现。
这样一来,我设计了适合于测试的代码,而又没有对代码本身施加太多的负担。实际上,由于许多工厂代码集中在一个地方,因此代码变得更加简洁。例如,如果我需要切换到实际代码中的另一个数据库客户端实现,则只需要修改一行,而不必四处寻找对构造函数的引用。
当然,就像乔恩(Jon)的方法一样,它不适用于您无法修改或不允许修改的第三方代码。
class DefaultMockupFactory implements MockupFactory {Timer createTimer() {return new Timer();}}
而已,那就不是那么复杂了吗?而且factory.createTimer()
在代码中的某处也不会使代码更难以理解。但我同意,在某些情况下,这可能不是最佳方法。
MockupFactory
有其他一些方法,并且我想查找代码中createTimer()
使用过的所有位置,则必须从被测类导航到接口,然后搜索用于方法的使用,而不仅仅是在类中搜索。
MockupFactory
对必须模拟的所有依赖项都使用一个接口(),而Jon建议为每个依赖项(TimerFactory
等等)使用单独的接口。在没有任何接口的情况下如何测试代码对我来说是一个谜。一种或另一种方式,需要额外的复杂性。
TimerFactory
会被重用,因此您可以将其连接到一个DI容器中一次,并在各处使用相同的接口,而MockupFactory
针对特定类的接口则不太可能可以在更多地方使用-与分隔良好的接口相比,这意味着需要更多的配置。