我有一些(C#)代码依赖于今天的日期来正确计算未来的东西。如果我在测试中使用今天的日期,我必须在测试中重复计算,这感觉不对。在测试中将日期设置为已知值的最佳方法是什么,以便我可以测试结果是否为已知值?
答案 0 :(得分:146)
我的偏好是让使用时间的类实际上依赖于接口,例如
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); } }
}
为依赖它的类提供时钟可能会有一些开销,但可以通过任意数量的依赖注入解决方案来处理(使用Inversion of Control容器,普通的构造函数/ setter注入,甚至是Static Gateway Pattern)。
提供提供所需时间的对象或方法的其他机制也有效,但我认为关键是避免重置系统时钟,因为这只会引入其他级别的痛苦。
此外,使用DateTime.Now
并将其包含在您的计算中并不仅仅是感觉不对 - 它会剥夺您测试特定时间的能力,例如,如果您发现仅在午夜边界附近发生的错误,或周二。使用当前时间将不允许您测试这些方案。或者至少不是随时随地。
答案 1 :(得分:55)
Ayende Rahien uses一种非常简单的静态方法......
public static class SystemTime
{
public static Func<DateTime> Now = () => DateTime.Now;
}
答案 2 :(得分:17)
我认为为简单的事情创建一个单独的时钟类就像获取当前日期一样有点矫枉过正。
您可以将今天的日期作为参数传递,这样您就可以在测试中输入不同的日期。这样可以使您的代码更加灵活。
答案 3 :(得分:16)
使用Microsoft Fakes创建填充程序是一种非常简单的方法。假设我有以下课程:
public class MyClass
{
public string WhatsTheTime()
{
return DateTime.Now.ToString();
}
}
在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);
}
}
}
}
答案 4 :(得分:10)
成功进行单元测试的关键是去耦。您必须将有趣的代码与其外部依赖项分开,因此可以单独进行测试。 (幸运的是,测试驱动开发会生成解耦代码。)
在这种情况下,您的外部是当前的DateTime。
我的建议是将处理DateTime的逻辑提取到新的方法或类或者你的情况下有意义的东西,然后传递DateTime。现在,你的单元测试可以传递一个任意的DateTime,来产生可预测的结果。
答案 5 :(得分:10)
另一个使用Microsoft Moles(Isolation framework for .NET)。
MDateTime.NowGet = () => new DateTime(2000, 1, 1);
Moles允许替换任何.NET 委托方法。摩尔支持 静态或非虚方法。痣 依赖于Pex的分析器。
答案 6 :(得分:4)
我建议使用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/
答案 7 :(得分:3)
简单回答:沟渠System.DateTime :)而是使用NodaTime及其测试库:NodaTime.Testing。
进一步阅读:
答案 8 :(得分:2)
您可以在正在测试的类中注入用于DateTime.Now
的类(更好:方法/ 委托)。将DateTime.Now
设置为默认值,并仅在测试中将其设置为返回常量值的虚拟方法。
编辑:布莱尔康拉德说的话(他有一些代码要看)。除此之外,我倾向于更喜欢代表,因为它们不会像IClock
这样的东西混淆你的类层次结构......
答案 9 :(得分: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的代码为here。
可以在here中找到使用Autofac的示例。
答案 10 :(得分:-10)
您是否考虑过使用条件编译来控制调试/部署期间发生的事情?
e.g。
DateTime date;
#if DEBUG
date = new DateTime(2008, 09, 04);
#else
date = DateTime.Now;
#endif
如果失败了,你想公开属性以便你可以操作它,这是编写可测试代码的挑战的一部分,这是我目前正在努力解决的问题:D
我的很大一部分会偏好Blair's approach。这允许您“热插拔”代码的一部分以帮助测试。这一切都遵循设计原则封装不同的测试代码与生产代码没有区别,它只是没有人在外部看到它。
创建和界面可能看起来像这个例子的很多工作(这就是我选择条件编译的原因)。