在测试期间覆盖DateTime.Now的好方法是什么?

Cra*_*col 109 c# testing datetime unit-testing

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

Bla*_*rad 149

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

interface IClock
{
    DateTime Now { get; } 
}
Run Code Online (Sandbox Code Playgroud)

具体实施

class SystemClock: IClock
{
     DateTime Now { get { return DateTime.Now; } }
}
Run Code Online (Sandbox Code Playgroud)

然后,如果您愿意,您可以提供您想要测试的任何其他类型的时钟,例如

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

为依赖它的类提供时钟可能会有一些开销,但可以通过任意数量的依赖注入解决方案来处理(使用Inversion of Control容器,普通的构造函数/ setter注入,甚至静态网关模式)).

提供提供所需时间的对象或方法的其他机制也有效,但我认为关键是避免重置系统时钟,因为这只会引入其他级别的痛苦.

此外,DateTime.Now在您的计算中使用和包含它不仅感觉不对 - 它会使您无法测试特定时间,例如,如果您发现仅在午夜边界附近或周二发生的错误.使用当前时间将不允许您测试这些方案.或者至少不是随时随地.

  • 很好的答案.只是想补充一点,在几乎所有情况下`UtcNow`应使用,然后根据代码的关注,如商业逻辑,UI等,适当调整时区跨越DateTime的操作是一个雷区,但最好先将左脚向前是始终以UTC时间开始. (8认同)
  • 实际上,我们在xUnit.net扩展之一中对此进行了形式化。我们有一个Clock类,您可以将其用作静态而不是DateTime,并且可以“冻结”和“解冻”时钟,包括指定日期。参见http://is.gd/3xds和http://is.gd/3xdu (2认同)
  • 值得注意的是,当您想要替换系统时钟方法时 - 例如,当在具有广泛分散的时区中的分支的企业中使用全局时钟时,这将发生 - 这种方法为您提供了宝贵的业务级自由来更改"现在"的含义. (2认同)

Ant*_*ean 55

Ayende Rahien 使用一种相当简单的静态方法......

public static class SystemTime
{
    public static Func<DateTime> Now = () => DateTime.Now;
}
Run Code Online (Sandbox Code Playgroud)

  • 恕我直言,首选使用界面而不是全局静态单例.考虑以下场景:测试运行器是高效的并且运行尽可能多的并行测试,一个测试更改为给定时间到X,另一个测试更改为Y.我们现在有冲突,这两个测试将切换失败.如果我们使用接口,每个测试根据他的需要模拟接口,现在每个测试都与其他测试隔离.HTH. (3认同)

Men*_*elt 17

我认为为简单的事情创建一个单独的时钟类,比如获取当前日期有点矫枉过正.

您可以将今天的日期作为参数传递,以便在测试中输入不同的日期.这样可以使您的代码更加灵活.


mmi*_*uva 16

使用Microsoft Fakes创建填充程序是一种非常简单的方法.假设我有以下课程:

public class MyClass
{
    public string WhatsTheTime()
    {
        return DateTime.Now.ToString();
    }

}
Run Code Online (Sandbox Code Playgroud)

在Visual Studio 2012中,您可以通过右键单击要为其创建Fakes/Shims的程序集并选择"Add Fakes Assembly",将Fakes程序集添加到测试项目中.

添加假动作组件

最后,这是测试类的样子:

using System;
using ConsoleApplication11;
using Microsoft.QualityTools.Testing.Fakes;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DateTimeTest
{
[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestWhatsTheTime()
    {

        using(ShimsContext.Create()){

            //Arrange
            System.Fakes.ShimDateTime.NowGet =
            () =>
            { return new DateTime(2010, 1, 1); };

            var myClass = new MyClass();

            //Act
            var timeString = myClass.WhatsTheTime();

            //Assert
            Assert.AreEqual("1/1/2010 12:00:00 AM",timeString);

        }
    }
}
}
Run Code Online (Sandbox Code Playgroud)

  • 这正是我正在寻找的。谢谢!顺便说一句,在 VS 2013 中的工作方式相同。 (2认同)

Jay*_*uzi 11

成功进行单元测试的关键是解耦.您必须将有趣的代码与其外部依赖项分开,因此可以单独进行测试.(幸运的是,测试驱动开发产生了解耦代码.)

在这种情况下,您的外部是当前的DateTime.

我的建议是将处理DateTime的逻辑提取到一个新的方法或类或在你的情况下有意义,并传递DateTime.现在,你的单元测试可以传递一个任意的DateTime,以产生可预测的结果.


Joã*_*elo 10

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

MDateTime.NowGet = () => new DateTime(2000, 1, 1);
Run Code Online (Sandbox Code Playgroud)

Moles允许用委托替换任何.NET方法.Moles支持静态或非虚拟方法.Moles依赖于Pex的剖析器.


Paw*_*ski 5

我建议使用 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);
    } 
}
Run Code Online (Sandbox Code Playgroud)

详细描述在这里:http : //www.lesnikowski.com/blog/index.php/testing-datetime-now/