在unit testing中处理DateTime.Now的策略

我的业务逻辑在周六或周日没有执行某些function。 我希望我的unit testing能够validation这些function是否已执行,但测试将在周六/周日失败。

我认为最简单的方法是让unit testing传达一条友好的信息,说明如果测试结果在周六/周日运行则无效。

用C#/ NUnit ……

Assert.That( DateTime.Now.DayOfWeek != DayOfWeek.Sunday && DateTime.Now.DayOfWeek != DayOfWeek.Saturday, "This test cannot be run on Saturday or Sunday"); 

试图嘲笑约会是否可行/可取? 是否有其他策略来处理这种情况?

试图嘲笑约会是否可行/可取?

是的,不仅是可行的,而且它是可靠地单独测试代码的唯一方法。 假设您要测试以下(无意义)方法:

 public bool Foo() { return (DateTime.Now.DayOfWeek == DayOfWeek.Sunday); } 

很明显,你无法测试它,因为它依赖于静态方法(静态Now属性更精确)。 很明显,像这样紧密耦合的组件不能单独进行unit testing。

现在考虑这种改进(与构造函数DI分离关注点):

 private readonly Func _nowProvider; public SomeClass(Func nowProvider) { _nowProvider = nowProvider; } public bool Foo() { return (_nowProvider().DayOfWeek == DayOfWeek.Sunday); } 

现在,unit testing更好更容易。 SomeClass不再依赖于非确定性日期。 在实际的应用程序中,您显然会像这样实例化SomeClass

 var s = new SomeClass(() => DateTime.Now); s.Foo(); 

在您的unit testing中,您将模拟它以便您可以validation这两种情况:

 var subjectUnderTest = new SomeClass(() => new DateTime(2011, 1, 3)); var actual = subjectUnderTest.Foo(); // assertions, ... 

您应该在系统时钟上创建一个抽象,因为您应该创建其他系统资源和外部资源的抽象,以便能够编写RTMunit testing 。

对系统时钟的抽象非常简单,看起来像这样:

 public interface ISystemClock { DateTime Now { get; } } 

应用程序中依赖于系统时钟的类通常应该具有ISystemClock作为构造函数参数。 对于unit testing场景,您可以使用如下所示的ISystemClock实现:

 public class FakeSystemClock : ISystemClock { // Note the setter. public DateTime Now { get; set; } } 

现在你可以在你的unit testing中使用这个假时钟,如下所示:

 [TestMethod] [ExpectedException(typeof(InvalidOperationException), "Service should throw an exception when called on saturday.")] public void DoSomething_WhenCalledOnSaturday_ThrowsException() { // Arrange var saturday = new DateTime(2011, 1, 1); Assert.AreEqual(saturday.DayOfWeek, DayOfWeek.Saturday, "Test setup fail"); var clock = new FakeSystemClock() { Now = saturday }; var service = new Service(clock); // Act service.DoSomething(); } 

在您的应用程序项目中,您可以定义实际使用系统时钟的ISystemClock实现。 使用给定的ISystemClock定义,它将如下所示:

 public class RealSystemClock : ISystemClock { public DateTime Now => DateTime.Now; } 

使用DI容器时,可以将其配置为连接类型,并在构造函数指定ISystemClock参数时自动注入RealSystemClock实例。

顺便说一句。 虽然您可以使用Func委托来完成这一操作,但为此定义一个接口更加明确,并且对其他开发人员来说更具可读性。 除此之外,将DI容器中的所有内容连接起来会容易得多,因为它很容易以多个Func依赖关系结束,所有这些都与“给我当前本地时间”不同。

我希望这有帮助。