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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/43711/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 09:53:52  来源:igfitidea点击:

What's a good way to overwrite DateTime.Now during testing?

提问by Craig.Nicol

I've got some (C#) code that relies on today's date to correctly calculate things in the future. If I use today's date in the testing, I have to repeat the calculation in the test, which doesn't feel right. What's the best way to set the date to a known value within the test so that I can test that the result is a known value?

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

采纳答案by Blair Conrad

My preference is to have classes that use time actually rely on an interface, such as

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

interface IClock
{
    DateTime Now { get; } 
}

With a concrete implementation

有具体的实施

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

Then if you want, you can provide any other kind of clock you want for testing, such as

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

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

There may be some overhead in providing the clock to the class that relies on it, but that could be handled by any number of dependency injection solutions (using an Inversion of Control container, plain old constructor/setter injection, or even a Static Gateway Pattern).

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

Other mechanisms of delivering an object or method that provides desired times also work, but I think the key thing is to avoid resetting the system clock, as that's just going to introduce pain on other levels.

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

Also, using DateTime.Nowand including it in your calculations doesn't just not feel right - it robs you of the ability to test particular times, for example if you discover a bug that only happens near a midnight boundary, or on Tuesdays. Using the current time won't allow you to test those scenarios. Or at least not whenever you want.

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

回答by Mendelt

I think creating a separate clock class for something simple like getting the current date is a bit overkill.

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

You can pass today's date as a parameter so you can input a different date in the test. This has the added benefit of making your code more flexible.

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

回答by Rob Cooper

Have you considered using conditional compilation to control what happens during debug/deployment?

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

e.g.

例如

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

Failing that, you want to expose the property so you can manipulate it, this is all part of the challenge of writing testablecode, which is something I am currently wrestling myself :D

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

Edit

编辑

A big part of me would preference Blair's approach. This allows you to "hot plug" parts of the code to aid in testing. It all follows the design principle encapsulate what variestest code is no different to production code, its just no one ever sees it externally.

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

Creating and interface may seem like a lot of work for this example though (which is why I opted for conditional compilation).

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

回答by Daren Thomas

You could inject the class (better: method/delegate) you use for DateTime.Nowin the class being tested. Have DateTime.Nowbe a default value and only set it in testing to a dummy method that returns a constant value.

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

EDIT:What Blair Conrad said (he has some code to look at). Except, I tend to prefer delegates for this, as they don't clutter up your class hierarchy with stuff like IClock...

编辑:布莱尔康拉德所说的(他有一些代码要看)。除此之外,我倾向于为此更喜欢委托,因为它们不会用诸如IClock......

回答by Anthony Mastrean

Ayende Rahien usesa static method that is rather simple...

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

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

回答by Jay Bazuzi

The key to successful unit testing is decoupling. You have to separate your interesting code from its external dependencies, so it can be tested in isolation. (Luckily, Test-Driven Development produces decoupled code.)

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

In this case, your external is the current DateTime.

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

My advice here is to extract the logic that deals with the DateTime to a new method or class or whatever makes sense in your case, and pass the DateTime in. Now, your unit test can pass an arbitrary DateTime in, to produce predictable results.

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

回答by Pawel Lesnikowski

I'd suggest using IDisposable pattern:

我建议使用 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);
    } 
}

In detail described here: http://www.lesnikowski.com/blog/index.php/testing-datetime-now/

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

回答by Jo?o Angelo

Another one using Microsoft Moles (Isolation framework for .NET).

另一个使用 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.

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

回答by mmilleruva

Using Microsoft Fakes to create a shim is a really easy way to do this. Suppose I had the following class:

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

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

}

In Visual Studio 2012 you can add a Fakes assembly to your test project by right clicking on the assembly you want to create Fakes/Shims for and selecting "Add Fakes Assembly"

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

Adding Fakes Assembly

添加假组件

Finally, Here is what the test class would look like:

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

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

        }
    }
}
}

回答by tugberk

Simple answer: ditch System.DateTime :) Instead, use NodaTimeand it's testing library: NodaTime.Testing.

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

Further reading:

进一步阅读: