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

时间:2011-01-04 14:27:00

标签: c# unit-testing datetime

我的业务逻辑在星期六或星期日没有执行某些功能。我希望我的单元测试能够验证这些功能是否已执行,但测试将在周六/周日失败。

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

使用C#/ NUnit ......

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

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

3 个答案:

答案 0 :(得分:9)

  

尝试模拟日期是否可行/可取?

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

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

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

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

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

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

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

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

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

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

答案 1 :(得分:2)

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

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

public interface ISystemClock
{
    DateTime Now { get; }
}

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

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

现在你可以在你的单元测试中使用这个假时钟:

[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容器时,可以将其配置为连接类型,并在构造函数指定RealSystemClock参数时自动注入ISystemClock的实例。

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

我希望这会有所帮助。

答案 2 :(得分:0)

Noda Time 可能是新项目的解决方案/替代方案,因为它都是基于界面的,因此很容易模拟。