在单元测试中处理DateTime.Now的策略

May*_*ayo 4 c# datetime 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");
Run Code Online (Sandbox Code Playgroud)

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

Dar*_*rov 9

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

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

public bool Foo()
{
    return (DateTime.Now.DayOfWeek == DayOfWeek.Sunday);
}
Run Code Online (Sandbox Code Playgroud)

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

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

private readonly Func<DateTime> _nowProvider;
public SomeClass(Func<DateTime> nowProvider)
{
    _nowProvider = nowProvider;
}

public bool Foo()
{
    return (_nowProvider().DayOfWeek == DayOfWeek.Sunday);
}
Run Code Online (Sandbox Code Playgroud)

现在,单元测试更好更容易.SomeClass不再依赖于非确定性日期.在实际应用程序中,您显然会实例化SomeClass如下:

var s = new SomeClass(() => DateTime.Now);
s.Foo();
Run Code Online (Sandbox Code Playgroud)

在您的单元测试中,您将模拟它以便您可以验证这两种情况:

var subjectUnderTest = new SomeClass(() => new DateTime(2011, 1, 3));
var actual = subjectUnderTest.Foo();
// assertions, ...
Run Code Online (Sandbox Code Playgroud)


Ste*_*ven 6

您应该在系统时钟上创建抽象,因为您应该在其他系统资源和外部资源上创建抽象,以便能够编写RTM 单元测试

对系统时钟的抽象非常简单,可能如下所示:

public interface ISystemClock
{
    DateTime Now { get; }
}
Run Code Online (Sandbox Code Playgroud)

应用程序中依赖于系统时钟的类通常应该有一个ISystemClockas 构造函数参数。对于单元测试场景,您可以使用如下所示的ISystemClock实现:

public class FakeSystemClock : ISystemClock
{
    // Note the setter.
    public DateTime Now { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

现在您可以在单元测试中使用这个假时钟,如下所示:

[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();
}
Run Code Online (Sandbox Code Playgroud)

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

public class RealSystemClock : ISystemClock
{
    public DateTime Now => DateTime.Now;
}
Run Code Online (Sandbox Code Playgroud)

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

顺便提一句。虽然您可以使用Func<DateTime>委托来实现这一点,但为此定义一个接口对于其他开发人员来说更加明确和更具可读性。除此之外,在 DI 容器中连接所有东西会容易得多,因为很容易最终得到多个Func<DateTime>依赖项,这些依赖项都做不同的事情,而不是“给我当前的本地时间”。

我希望这有帮助。