什么是在测试过程中覆盖DateTime.Now的好方法?

时间:2020-03-05 18:47:35  来源:igfitidea点击:

我有一些(C#)代码依赖于今天的日期来正确计算将来的事情。如果我在测试中使用今天的日期,则必须在测试中重复计算,感觉不对。在测试中将日期设置为已知值的最佳方法是什么,以便我可以测试结果是否为已知值?

解决方案

回答

我认为为诸如获取当前日期之类的简单事件创建单独的时钟类有点过头了。

我们可以将今天的日期作为参数传递,以便可以在测试中输入其他日期。这具有使代码更灵活的添加好处。

回答

我们是否考虑过使用条件编译来控制调试/部署期间发生的情况?

例如

DateTime date;
#if DEBUG
  date = new DateTime(2008, 09, 04);
#else
  date = DateTime.Now;
#endif

否则,我们想公开该属性以便可以对其进行操作,这就是编写可测试代码的全部挑战,而这正是我目前正在努力解决的问题:D

编辑

我很大一部分会喜欢布莱尔的方法。这使我们可以"热插拔"部分代码以帮助测试。一切都遵循设计原理封装各种测试代码与生产代码没有什么不同,只是没有人从外部看到过它。

对于本示例,创建和接口似乎需要大量工作(这就是为什么我选择条件编译的原因)。

回答

我的首选是让使用时间的类实际上依赖于接口,例如

interface IClock
{
    DateTime Now { get; } 
}

具体实施

class SystemClock: IClock
{
     DateTime Now { get { return DateTime.Now; } }
}

然后,如果需要,我们可以提供任何其他想要测试的时钟,例如

class StaticClock: IClock
{
     DateTime Now { get { return new DateTime(2008, 09, 3, 9, 6, 13); } }
}

向依赖它的类提供时钟可能会有一些开销,但是可以通过任何数量的依赖项注入解决方案来处理(使用控制容器反转,普通的旧构造函数/ setter注入,甚至是静态网关模式) )。

交付提供所需时间的对象或者方法的其他机制也可以使用,但是我认为关键是要避免重置系统时钟,因为这只会给其他层次带来麻烦。

另外,使用" DateTime.Now"并将其包含在计算中不仅会带来不适感,还会使我们失去测试特定时间的能力,例如,如果我们发现仅在午夜边界或者星期二附近发生的错误。使用当前时间将无法测试这些情况。或者至少不在我们想要的任何时候。

回答

我们可以在测试的类中注入用于" DateTime.Now"的类(更好的方法/委托)。将" DateTime.Now"设置为默认值,并且仅在测试时将其设置为返回恒定值的伪方法。

编辑:布莱尔·康拉德(Blair Conrad)所说的(他有一些代码要看)。除此之外,我倾向于使用委托,因为它们不会像IClock这样的东西使类层次结构混乱。

回答

Ayende Rahien使用一种非常简单的静态方法...

public static class SystemTime
{
    public static Func<DateTime> Now = () => DateTime.Now;
}

回答

成功进行单元测试的关键是去耦。我们必须将我们感兴趣的代码与其外部依赖性分开,以便可以对其进行单独测试。 (幸运的是,测试驱动开发会产生解耦的代码。)

在这种情况下,外部设备是当前的DateTime。

我的建议是将处理DateTime的逻辑提取到新方法或者类或者我们认为合适的任何东西,并传递DateTime。现在,单元测试可以传递任意DateTime,以产生可预测的结果。

回答

我建议使用IDisposable模式:

[Test] 
public void CreateName_AddsCurrentTimeAtEnd() 
{
    using (Clock.NowIs(new DateTime(2010, 12, 31, 23, 59, 00)))
    {
        string name = new ReportNameService().CreateName(...);
        Assert.AreEqual("name 2010-12-31 23:59:00", name);
    } 
}

这里详细介绍:
http://www.lesnikowski.com/blog/index.php/testing-datetime-now/

回答

另一个使用Microsoft Moles(.NET的隔离框架)。

MDateTime.NowGet = () => new DateTime(2000, 1, 1);
Moles allows to replace any .NET
  method with a delegate. Moles supports
  static or non-virtual methods. Moles
  relies on the profiler from Pex.