提问人:Craig.Nicol 提问时间:9/4/2008 最后编辑:Alex BCraig.Nicol 更新时间:6/22/2023 访问量:49516
在测试期间覆盖 DateTime.Now 的好方法是什么?
What's a good way to overwrite DateTime.Now during testing?
问:
我有一些 (C#) 代码,它依赖于今天的日期来正确计算未来的事情。如果我在测试中使用今天的日期,我必须在测试中重复计算,这感觉不对。在测试中将日期设置为已知值以便测试结果是否为已知值的最佳方法是什么?
答:
我认为为获取当前日期等简单的事情创建一个单独的时钟类有点矫枉过正。
您可以将今天的日期作为参数传递,以便在测试中输入不同的日期。这还有一个额外的好处,就是使你的代码更加灵活。
评论
您是否考虑过使用条件编译来控制调试/部署期间发生的情况?
例如:
DateTime date;
#if DEBUG
date = new DateTime(2008, 09, 04);
#else
date = DateTime.Now;
#endif
如果做不到这一点,你就想公开属性,这样你就可以操作它,这都是编写可测试代码的挑战的一部分,这是我目前正在努力解决的问题:D
编辑
我很大一部分人更喜欢布莱尔的方法。这允许您“热插拔”部分代码以帮助进行测试。这一切都遵循设计原则,封装了各种测试代码与生产代码没有什么不同,只是没有人在外部看到它。
不过,对于这个例子来说,创建和接口似乎需要做很多工作(这就是我选择条件编译的原因)。
评论
DateTime
Now
Today
我的偏好是让使用时间的类实际上依赖于一个接口,例如
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
评论
UtcNow
您可以在正在测试的类中注入您使用的类(更好:方法/委托)。必须为默认值,并且仅在测试中将其设置为返回常量值的虚拟方法。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);
鼹鼠允许替换任何 .NET 方法。鼹鼠支架 静态或非虚拟方法。摩尔 依赖于 Pex 的分析器。
评论
使用 Microsoft Fakes 创建填充码是一种非常简单的方法。假设我有以下类:
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);
}
}
}
}
评论
简单的答案:抛弃 System.DateTime :)相反,请使用 NodaTime 及其测试库:NodaTime.Testing。
延伸阅读:
我经常遇到这种情况,以至于我创建了简单的 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 的使用示例。
只需将 DateTime.Now 作为参数传递给需要它的方法即可。没有比这更简单的了。https://blog.thecodewhisperer.com/permalink/beyond-mock-objects
.NET 8 引入了另一种方法。你可以在这里阅读更多关于它的信息。
此外,Microsoft.Extensions.TimeProvider.Testing 公开了可在测试中使用的类。FakeTimeProvider
下面是一个示例:
public class ServiceClass
{
private readonly TimeProvider _timeProvider;
public TimeExperiments(TimeProvider timeProvider)
{
this._timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
}
public void ServiceMethod()
{
long timestamp = this._timeProvider.GetTimestamp();
DateTimeOffset localTime = this._timeProvider.GetLocalNow();
DateTimeOffset utcTime = this._timeProvider.GetUtcNow();
// Later you can use time-dependent data throughout the `this._timeProvider` instance.
}
}
这是如何使用:FakeTimeProvider
public void TestServiceMethod()
{
// This creates a clock whose time is set to midnight January 1st 2000.
var fakeTimeProvider = new FakeTimeProvider();
var service = new ServiceClass(fakeTimePRovider);
// Later you can use this service.
}
注意:这仍然是一项实验性功能,因为 .NET 8 仍处于预览状态。
评论