在testing过程中覆盖DateTime.Now的好方法是什么?
我有一些(C#)代码依赖今天的date来正确计算未来的事情。 如果我在testing中使用今天的date,我不得不在testing中重复计算,这是不正确的。 在testing中将date设置为已知值的最佳方法是什么,以便我可以testing结果是已知值?
我的首选是使用时间的类实际上依赖于一个接口,如
interface IClock { DateTime Now { get; } }
具体实施
class SystemClock: IClock { DateTime Now { get { return DateTime.Now; } } }
那么如果你想,你可以提供任何其他types的时钟,你想testing,如
class StaticClock: IClock { DateTime Now { get { return new DateTime(2008, 09, 3, 9, 6, 13); } } }
提供时钟给依赖它的类可能会有一些开销,但是这可以通过任意数量的dependency injection解决scheme来处理(使用Inversion of Control容器,简单的旧构造函数/ setter注入,甚至静态网关模式 )。
提供所需时间的对象或方法的其他机制也是可行的,但我认为关键是避免重新设置系统时钟,因为这会引起其他层面的痛苦。
另外,使用DateTime.Now
并将其包含在您的计算中并不仅仅是不正确,而且会让您失去testing特定时间的能力,例如,如果发现只发生在午夜边界附近或周二的错误。 使用当前时间将不允许您testing这些情况。 或者至less不是每当你想要的时候。
Ayende Rahien 使用一种相当简单的静态方法…
public static class SystemTime { public static Func<DateTime> Now = () => DateTime.Now; }
我认为创build一个单独的时钟类,就像获取当前date那样简单,这有点矫枉过正。
您可以将今天的date作为parameter passing,以便您可以在testing中input不同的date。 这有助于使代码更加灵活。
使用微软假货创build一个垫片是一个非常简单的方法来做到这一点。 假设我有以下课程:
public class MyClass { public string WhatsTheTime() { return DateTime.Now.ToString(); } }
在Visual Studio 2012中,您可以通过右键单击要创buildFakes / Shims的程序集并select“Add Fakes Assembly”来添加Fakes程序集到您的testing项目中
最后,这是testing类的样子:
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); } } } }
成功的unit testing的关键是解耦 。 你必须将你感兴趣的代码从外部的依赖中分离出来,这样就可以孤立地进行testing了。 (幸运的是,testing驱动开发产生解耦的代码。)
在这种情况下,您的外部是当前date时间。
我的build议是将处理DateTime的逻辑提取到一个新的方法或类,或者在你的情况下有意义的东西,然后传入DateTime。现在,你的unit testing可以传入一个任意的DateTime来产生可预测的结果。
另一个使用Microsoft Moles( .NET的隔离框架 )。
MDateTime.NowGet = () => new DateTime(2000, 1, 1);
Moles允许用委托来replace任何.NET方法。 痣支持静态或非虚拟方法。 鼹鼠依靠Pex的分析器。
我build议使用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/
简单的答案:沟System.DateTime :)相反,使用NodaTime和它的testing库: NodaTime.Testing 。
进一步阅读:
- 用野田时间unit testing
- dependency injection:注入你的依赖关系,周期!
你可以在正在testing的类中注入用于DateTime.Now
的类(更好:方法/ 委托 )。 有DateTime.Now
是一个默认值,只能在testing中设置一个返回一个常量值的虚拟方法。
编辑:什么布莱尔·康拉德说(他有一些代码看)。 除此之外,我倾向于喜欢代表,因为它们不会像IClock
那样IClock
你的类层次结构。
我喜欢你的解决scheme@Blair。
注入System.DateTime不会妨碍您的具体实现,但有时IClock或IDateTime允许您模拟您的unit testing。
下面是您可能会发现的大部分System.DateTime公共成员的提取界面: IDateTime
您是否考虑过使用条件编译来控制debugging/部署过程中发生的情况?
例如
DateTime date; #if DEBUG date = new DateTime(2008, 09, 04); #else date = DateTime.Now; #endif
如果不这样做,你想暴露的属性,所以你可以操纵它,这是编写可testing代码的挑战的一部分,这是我目前正在摔跤自己:D
编辑
我的很大一部分会喜欢布莱尔的方法 。 这使您可以“热插”部分代码来帮助testing。 这一切都遵循devise原则封装了各种testing代码与生产代码无异,它从来没有人看到过它的外部。
虽然创build和接口可能看起来像很多工作(这就是为什么我select了条件编译)。