当代码根据当前日期处理日期时,测试应涵盖边缘情况,例如闰年以及更频繁的月份和年份边界。
在我们的代码中,我们总是使用DateTime.Now
(在我们的例子中是.NET)深入了解我们的类中的当前日期。
如何对这些代码进行单元测试?
这是Dependency Injection变得非常有用吗?
这是暂时的,但显然下一版的Typemock将允许伪造DateTime.Now
https://blog.typemock.com/2009/05/mockingfaking-datetimenow-in-unit-tests.html
答案 0 :(得分:15)
在我们的代码中,我们总是使用DateTime.Now(在我们的例子中是.NET)来提取当前日期。你如何对这些代码进行单元测试?
这是一种依赖关系,并且是一种非确定性的依赖关系。您需要将代码的责任分开一点。
在:
后:
这两组代码不应相互依赖。
这种分离依赖关系的模式也适用于其他情况(数据库,文件系统等)。
答案 1 :(得分:12)
使用DI注入“当前日期和时间”肯定是一种过度杀伤力。我宁愿重构代码以在任意日期操作。也就是说,我将function calculateRevenue()
更改为function calculateRevenueOn(datetime date)
。如果您需要计算除当前日期之外的某些日期,这将更容易测试和使用。
答案 2 :(得分:10)
我使用了Oren Eini(又名Ayende Rahien)在他的博客点Dealing with time in tests中讨论的非常实用的方法。
有一个像这样的静态类:
public static class SystemTime
{
public static Func<DateTime> Now = () => DateTime.Now;
}
你的代码变成了:
Entity.LastChange = SystemTime.Now();
你的测试将成为:
SystemTime.Now = () => new DateTime(2000,1,1);
repository.ResetFailures(failedMsgs);
SystemTime.Now = () => new DateTime(2000,1,2);
var msgs = repository.GetAllReadyMessages();
Assert.AreEqual(2, msgs.Length);
答案 3 :(得分:9)
始终将核心日期处理逻辑(根据我的经验,通常很容易将其隔离)放入以日期作为参数的单独方法中。
然后,在您的生产代码中,您可以调用它们并将它们作为参数提供给当前日期,并且仍然可以在单元测试中测试边缘情况。
顺便说一句,我发现测试边缘情况的日期逻辑是非常重要的。它消除了(可能是灾难性的,参考Zune)你从未发现的错误。除了你提到的那些,夏令时转换也可能有问题。答案 4 :(得分:3)
你可以创建一个模拟DateTime.Now
的模拟对象这里有一篇文章,其中有一个例子,其中DateTime.Now被模拟:http://geekswithblogs.net/AzamSharp/archive/2008/04/27/121695.aspx
答案 5 :(得分:3)
你已经给出了答案。您可以在datetime函数周围编写一个小包装类,然后可以在需要获取当前日期的类中注入。用调用包装器对象替换DateTime.Now。
在测试中,您可以注入一个存根或模拟对象,为您提供所需的日期。
另一种解决方案可能是,根据代码的工作方式,只需将日期作为参数传递,而不是将调用挂载到datetime.Now。这使您的代码更可重用,因为它可以在当前日期以上工作
答案 6 :(得分:2)
public interface ITimeProvider {
DateTime Now { get; }
}
public class SystemTimeProvider : ITimeProvider {
public virtual DateTime Now { get { return DateTime.Now; } }
}
public class MockTimeProvider : ITimeProvider {
public virtual DateTime Now { get; set; }
}
public class ServiceThatDependsOnTime {
protected virtual ITimeProvider { get; set; }
public ServiceThatDependsOnTime(ITimeProvider timeProvider) {
TimeProvider = timeProvider;
}
public virtual void DoSomeTimeDependentOperation() {
var now = TimeProvider.Now;
//use 'now' in some complex processing.
}
}
[Test]
public virtual void TestTheService() {
var time = new MockTimeProvider();
time.Now = DateTime.Now.AddDays(-500);
var service = new ServiceThatDependsOnTime(time);
service.DoSomeTimeDependentOperation();
//check that the service did it right.
}
答案 7 :(得分:1)
为什么不使用假货?使用Visual Studio尽可能简单。只需将假装配添加到System,将ShimsContext添加到测试中,
using (ShimsContext.Create())
{
并设置DateTime.Now以返回特定值:
System.Fakes.ShimDateTime.NowGet = () => new DateTime(2000, 1, 1);
这种方法确保每次调用DateTime.Now都会重定向到您的代码,因此您不必注入任何东西来获取当前时间(这应该是非常直接的),也不需要全局可写变量。
答案 8 :(得分:0)
依赖注入可以解决这个问题,是的。
您要做的是使用方法:GetDate()创建IDateTimeProvider接口。在生产代码类中,您将实现return DateTime.Now。
当Natrium建议进行单元测试时,您可以将其替换为模拟对象,该对象返回要测试的特定日期。