单元测试代码,根据今天的日期进行日期处理

时间:2009-02-19 13:27:29

标签: unit-testing datetime language-agnostic dependency-injection

当代码根据当前日期处理日期时,测试应涵盖边缘情况,例如闰年以及更频繁的月份和年份边界。

在我们的代码中,我们总是使用DateTime.Now(在我们的例子中是.NET)深入了解我们的类中的当前日期。

如何对这些代码进行单元测试?

这是Dependency Injection变得非常有用吗?

修改

这是暂时的,但显然下一版的Typemock将允许伪造DateTime.Now

https://blog.typemock.com/2009/05/mockingfaking-datetimenow-in-unit-tests.html

9 个答案:

答案 0 :(得分:15)

  

在我们的代码中,我们总是使用DateTime.Now(在我们的例子中是.NET)来提取当前日期。你如何对这些代码进行单元测试?

这是一种依赖关系,并且是一种非确定性的依赖关系。您需要将代码的责任分开一点。

在:

  • 有些代码使用当前日期时间来执行X.

后:

  • 应该有一些代码负责获取当前日期时间。
  • 应该有一些代码使用日期时间来执行X.

这两组代码不应相互依赖。

这种分离依赖关系的模式也适用于其他情况(数据库,文件系统等)。

答案 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建议进行单元测试时,您可以将其替换为模拟对象,该对象返回要测试的特定日期。