124

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

4

11 回答 11

163

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

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); } }
}

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

提供提供所需时间的对象或方法的其他机制也可以工作,但我认为关键是避免重置系统时钟,因为这只会在其他层面上带来痛苦。

此外,DateTime.Now在您的计算中使用并包含它不仅感觉不对 - 它会剥夺您测试特定时间的能力,例如,如果您发现仅在午夜边界附近或星期二发生的错误。使用当前时间将不允许您测试这些场景。或者至少不是你想要的时候。

于 2008-09-04T13:06:28.843 回答
57

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

public static class SystemTime
{
    public static Func<DateTime> Now = () => DateTime.Now;
}
于 2008-09-04T13:14:59.947 回答
23

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

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

}

在 Visual Studio 2012 中,您可以通过右键单击要为其创建 Fakes/Shims 的程序集并选择“添加 Fakes 程序集”来将 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);

        }
    }
}
}
于 2014-01-03T14:56:14.280 回答
17

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

您可以将今天的日期作为参数传递,以便在测试中输入不同的日期。这具有使您的代码更加灵活的额外好处。

于 2008-09-04T13:04:32.610 回答
12

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

在这种情况下,您的外部是当前日期时间。

我的建议是将处理 DateTime 的逻辑提取到新方法或类或任何对你的情况有意义的东西,然后传入 DateTime。现在,你的单元测试可以传入任意 DateTime,以产生可预测的结果。

于 2008-09-09T14:53:26.733 回答
11

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

MDateTime.NowGet = () => new DateTime(2000, 1, 1);

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

于 2010-02-02T12:09:56.667 回答
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);
    } 
}

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

于 2010-02-02T11:52:08.527 回答
4

简单的答案:抛弃 System.DateTime :) 相反,使用NodaTime和它的测试库:NodaTime.Testing

进一步阅读:

于 2014-11-18T12:23:59.213 回答
3

您可以在被测试的类中注入您使用的类(更好:方法/委托) 。DateTime.NowHaveDateTime.Now是默认值,并且仅在测试中将其设置为返回常量值的虚拟方法。

编辑:布莱尔康拉德所说的(他有一些代码要看)。IClock除了,我倾向于为此更喜欢委托,因为它们不会用诸如...之类的东西弄乱你的类层次结构。

于 2008-09-04T13:06:45.660 回答
1

我经常遇到这种情况,所以我创建了简单的 nuget,它通过接口公开Now属性。

public interface IDateTimeTools
{
    DateTime Now { get; }
}

实现当然非常简单

public class DateTimeTools : IDateTimeTools
{
    public DateTime Now => DateTime.Now;
}

因此,将 nuget 添加到我的项目后,我可以在单元测试中使用它

在此处输入图像描述

您可以直接从 GUI Nuget 包管理器或使用以下命令安装模块:

Install-Package -Id DateTimePT -ProjectName Project

Nuget 的代码在这里

可以在这里找到 Autofac 的使用示例。

于 2018-06-27T13:43:42.477 回答
-10

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

例如

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

如果做不到这一点,您想公开该属性以便您可以操纵它,这是编写可测试代码的挑战的一部分,这是我目前正在努力解决的问题:D

编辑

我很喜欢布莱尔的方法。这允许您“热插拔”部分代码以帮助测试。这一切都遵循设计原则,封装了不同的测试代码与生产代码没有什么不同,只是没有人在外部看到它。

不过,对于这个例子来说,创建和接口似乎需要做很多工作(这就是我选择条件编译的原因)。

于 2008-09-04T13:05:53.463 回答