使用 DateTimeOffset 对象的单元测试 类 的正确方法?

Correct way of unit-testing classes that use DateTimeOffset objects?

有关如何正确测试使用 DateTimeOffset 实例的代码的信息或示例,我将不胜感激。我知道测试必须是确定性的。

那么,如何将应用程序与 DateTimeOffset classes 隔离开来?当然,我希望能够使用假的 DateTimeOffset.Now,等等

在我的测试中,我应该使用类似的东西吗:

var myDate = new DateTimeOffset(2016, 3, 29, 12, 20, 35, 93, TimeSpan.FromHours(-3));

或者我会改用像 MyCustomDateTimeOffset 这样的包装器 class 吗? 我是否应该在我的代码中完全不使用 DateTimeOffset 而是使用包装器?

正如 fundamentals theorem 所说:

We can solve any problem by introducing an extra level of indirection.

你真的不需要包装器,你需要的只是避免 DateTimeOffset.Now/DateTimeOffset.UtcNow.

您可以通过以下几种方式处理该问题:

  • 如果你使用依赖注入,写一个IClock接口暴露Now/UtcNow属性。

    public interface IClock
    {
        DateTimeOffset Now { get; }
        DateTimeOffset UtcNow { get; }
    }
    
    internal class Clock : IClock
    {
        public DateTimeOffset Now => DateTimeOffset.Now;
        public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
    }
    

    在您的测试中,您可以根据需要模拟接口。

  • 如果你宁愿继续使用静态 属性,写一个静态类型,比方说 Clock,然后使用它。

    public static class Clock
    {
        internal static Func<DateTimeOffset> DateTimeOffsetProvider { get; set; }
            = () => DateTimeOffset.Now;
    
        public static DateTimeOffset Now => DateTimeOffsetProvider();
        public static DateTimeOffset UtcNow => DateTimeOffsetProvider().ToUniversalTime();
    }
    

    在您的测试中,您可以替换 DateTimeOffsetProvider

    这是 .NET 2 版本:

    public static class Clock
    {
        internal delegate DateTimeOffset DateTimeOffsetProviderDelegate();
        internal static DateTimeOffsetProviderDelegate DateTimeOffsetProvider { get; set; }
    
        public static DateTimeOffset Now { get { return DateTimeOffsetProvider(); } }
        public static DateTimeOffset UtcNow { get { return DateTimeOffsetProvider().ToUniversalTime(); } }
    
        static Clock()
        {
            DateTimeOffsetProvider = delegate() { return DateTimeOffset.Now; };
        }
    }
    

因为您不知道 DateTimeOffSet.Now 的值,所以您不能断言 DateTimeOffSet.Now 等于一个值。

您可能应该使用以下两种方法之一进行重构:

  • 依赖注入
  • 接口和包装器

依赖注入(DI)

DI 表示不是让方法确定日期,而是将其传入。

这个方法。 . .

public void DoSomething()
{
   var now = DateTimeOffSet.Now;
   // Do other stuff with the date
}

。 . .将更改为此方法

public void DoSomething(DateTimeOffSet dtos)
{
   // Do other stuff with the date
}

接口和包装器

您的另一个选择(尽管最后您也会使用 DI)是创建一个接口和一个包装器。然后使用对象中的接口而不是具体的 DateTimeOffSet,这样您就可以使用 MOQ 或其他测试库来最小化接口。查看 SystemWrapper (https://github.com/jozefizso/SystemWrapper) 项目作为示例。