单元测试:DateTime.Now

Ped*_*dro 146 c# datetime unit-testing mstest systemtime

我有一些单元测试,希望'当前时间'与DateTime不同.现在我不想改变计算机的时间.

实现这一目标的最佳策略是什么?

Mar*_*ann 198

最好的策略是把当前时间的抽象和注入是抽象到消费者.


或者,您也可以将时间抽象定义为环境上下文:

public abstract class TimeProvider
{
    private static TimeProvider current =
        DefaultTimeProvider.Instance;

    public static TimeProvider Current
    {
       get { return TimeProvider.current; }
       set 
       {
           if (value == null)
           {
               throw new ArgumentNullException("value");
           }
           TimeProvider.current = value; 
       }
   }

   public abstract DateTime UtcNow { get; }

   public static void ResetToDefault()
   {    
       TimeProvider.current = DefaultTimeProvider.Instance;
   }            
}
Run Code Online (Sandbox Code Playgroud)

这将使您能够像这样使用它:

var now = TimeProvider.Current.UtcNow;
Run Code Online (Sandbox Code Playgroud)

在单元测试中,您可以TimeProvider.Current使用Test Double/Mock对象替换.使用Moq的示例:

var timeMock = new Mock<TimeProvider>();
timeMock.SetupGet(tp => tp.UtcNow).Returns(new DateTime(2010, 3, 11));
TimeProvider.Current = timeMock.Object;
Run Code Online (Sandbox Code Playgroud)

但是,当使用静态进行单元测试时,请务必通过调用来拆除夹具TimeProvider.ResetToDefault().

  • 对于环境上下文,即使您确实拆除了,如何使测试并行运行? (6认同)
  • @MikeK正如我的回答中的第一句话所说:*最好的策略是将当前时间包装在抽象中并将抽象注入消费者.*这个答案中的其他所有内容都是第二好的选择. (4认同)
  • @IlyaChernomordik 你[不应该注入 ILogger 或其他交叉问题](http://stackoverflow.com/a/7906547/126014),所以注入时间提供者仍然是最好的选择。 (2认同)
  • 你好 我菜鸟 如何使用此解决方案阻止人们访问DateTime.UtcNow?有人会很容易错过使用TimeProvider的机会。从而导致测试中的错误? (2认同)
  • @Obbles 代码审查(或结对编程,这是实时代码审查的一种形式)。我想人们可以编写一个工具来扫描`DateTime.UtcNow` 和类似的代码库,但无论如何代码审查是一个好主意。 (2认同)

cra*_*TOR 54

这些都是很好的答案,这就是我在另一个项目上所做的:

用法:

获取今天的真实日期时间

var today = SystemTime.Now().Date;
Run Code Online (Sandbox Code Playgroud)

而不是使用DateTime.Now,你需要使用SystemTime.Now()......这不是很难改变,但这个解决方案可能并不适合所有项目.

时间旅行(让我们将来5年)

SystemTime.SetDateTime(today.AddYears(5));
Run Code Online (Sandbox Code Playgroud)

今天"假装"(距离"今天"5年)

var fakeToday = SystemTime.Now().Date;
Run Code Online (Sandbox Code Playgroud)

重置日期

SystemTime.ResetDateTime();
Run Code Online (Sandbox Code Playgroud)
/// <summary>
/// Used for getting DateTime.Now(), time is changeable for unit testing
/// </summary>
public static class SystemTime
{
    /// <summary> Normally this is a pass-through to DateTime.Now, but it can be overridden with SetDateTime( .. ) for testing or debugging.
    /// </summary>
    public static Func<DateTime> Now = () => DateTime.Now;

    /// <summary> Set time to return when SystemTime.Now() is called.
    /// </summary>
    public static void SetDateTime(DateTime dateTimeNow)
    {
        Now = () =>  dateTimeNow;
    }

    /// <summary> Resets SystemTime.Now() to return DateTime.Now.
    /// </summary>
    public static void ResetDateTime()
    {
        Now = () => DateTime.Now;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 非常危险.这可能会影响并行运行的其他单元测试. (10认同)
  • @crabCRUSHERclamCOLI:如果您从http://ayende.com/blog/3408/dealing-with-time-in-tests获得了这个想法,那么链接到它就是一件好事. (5认同)
  • 这个概念也适用于模拟Guid生成(public static Func <Guid> NewGuid =()=> Guid.NewGuid(); (3认同)
  • 您还可以添加此有用的方法:public static void ResetDateTime(DateTime dateTimeNow){var timespanDiff = TimeSpan.FromTicks(DateTime.Now.Ticks - dateTimeNow.Ticks); Now =()=> DateTime.Now - timespanDiff; } (2认同)

Pel*_*eli 24

摩尔:

[Test]  
public void TestOfDateTime()  
{  
      var firstValue = DateTime.Now;
      MDateTime.NowGet = () => new DateTime(2000,1,1);
      var secondValue = DateTime.Now;
      Assert(firstValue > secondValue); // would be false if 'moleing' failed
}
Run Code Online (Sandbox Code Playgroud)

免责声明 - 我在研究Moles

  • 看起来 Moles 现在不再受支持,取而代之的是 Fakes https://msdn.microsoft.com/en-us/library/hh549175%28v=vs.110%29.aspx?f=255&amp;MSPPError=-2147217396,毫无疑问 MS 将停止使用太快了 (3认同)

Eli*_*sha 16

你有一些选择:

  1. 使用模拟框架并使用DateTimeService(实现一个小的包装类并将其注入生产代码).包装器实现将访问DateTime,在测试中,您将能够模拟包装器类.

  2. 使用Typemock Isolator,它可以伪造DateTime.Now,不需要你更改测试中的代码.

  3. 使用Moles,它也可以伪造DateTime.Now并且不需要更改生产代码.

一些例子:

使用Moq的包装类:

[Test]
public void TestOfDateTime()
{
     var mock = new Mock<IDateTime>();
     mock.Setup(fake => fake.Now)
         .Returns(new DateTime(2000, 1, 1));

     var result = new UnderTest(mock.Object).CalculateSomethingBasedOnDate();
}

public class DateTimeWrapper : IDateTime
{
      public DateTime Now { get { return DateTime.Now; } }
}
Run Code Online (Sandbox Code Playgroud)

使用Isolator直接伪造DateTime:

[Test]
public void TestOfDateTime()
{
     Isolate.WhenCalled(() => DateTime.Now).WillReturn(new DateTime(2000, 1, 1));

     var result = new UnderTest().CalculateSomethingBasedOnDate();
}
Run Code Online (Sandbox Code Playgroud)

免责声明 - 我在Typemock工作


dav*_*ave 10

为System添加假装配(右键单击System reference => Add false assembly).

并写入您的测试方法:

using (ShimsContext.Create())
{
   System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10);
   MethodThatUsesDateTimeNow();
}
Run Code Online (Sandbox Code Playgroud)

  • 需要 Visual Studio Enterprise。更多详细信息如何创建和使用 [Microsoft fakers](https://learn.microsoft.com/en-us/visualstudio/test/isolated-code-under-test-with-microsoft-fakes?view=vs-2019) (2认同)

Nir*_*Nir 9

您可以更改正在测试的类以使用 a ,Func<DateTime>它将通过其构造函数参数传递,因此当您在实际代码中创建该类的实例时,您可以传递() => DateTime.UtcNow给该Func<DateTime>参数,并且在测试时,您可以传递您的时间希望测试。

例如:

    [TestMethod]
    public void MyTestMethod()
    {
        var instance = new MyClass(() => DateTime.MinValue);
        Assert.AreEqual(instance.MyMethod(), DateTime.MinValue);
    } 

    public void RealWorldInitialization()
    {
        new MyClass(() => DateTime.UtcNow);
    }

    class MyClass
    {
        private readonly Func<DateTime> _utcTimeNow;

        public MyClass(Func<DateTime> UtcTimeNow)
        {
            _utcTimeNow = UtcTimeNow;
        }

        public DateTime MyMethod()
        {
            return _utcTimeNow();
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这个答案。不过,根据我收集到的信息,这在 C# 世界中可能会令人大吃一惊,因为 C# 世界更关注类/对象。不管怎样,我更喜欢这个,因为它对我来说非常简单明了正在发生的事情。 (2认同)

mar*_*inn 6

关于@crabcrusherclamcollector答案,在EF查询中使用该方法时存在问题(System.NotSupportedException:LINQ to Entities中不支持LINQ表达式节点类型'Invoke').我修改了实现:

public static class SystemTime
    {
        private static Func<DateTime> UtcNowFunc = () => DateTime.UtcNow;

        public static void SetDateTime(DateTime dateTimeNow)
        {
            UtcNowFunc = () => dateTimeNow;
        }

        public static void ResetDateTime()
        {
            UtcNowFunc = () => DateTime.UtcNow;
        }

        public static DateTime UtcNow
        {
            get
            {
                DateTime now = UtcNowFunc.Invoke();
                return now;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)


per*_*ife 6

为了测试依赖于一个代码System.DateTime,则system.dll必须嘲笑。

我知道有两个框架可以做到这一点。微软的假货Smocks

微软假冒产品需要Visual Studio 2012最后通and,并且可以直接在康普顿中使用。

Smocks是开源的,非常易于使用。可以使用NuGet下载。

下面显示了一个模拟System.DateTime

Smock.Run(context =>
{
  context.Setup(() => DateTime.Now).Returns(new DateTime(2000, 1, 1));

   // Outputs "2000"
   Console.WriteLine(DateTime.Now.Year);
});
Run Code Online (Sandbox Code Playgroud)


Hen*_*jen 5

线程安全SystemClock使用ThreadLocal<T>对我来说很有用.

ThreadLocal<T> 可以在.Net Framework v4.0及更高版本中使用.

/// <summary>
/// Provides access to system time while allowing it to be set to a fixed <see cref="DateTime"/> value.
/// </summary>
/// <remarks>
/// This class is thread safe.
/// </remarks>
public static class SystemClock
{
    private static readonly ThreadLocal<Func<DateTime>> _getTime =
        new ThreadLocal<Func<DateTime>>(() => () => DateTime.Now);

    /// <inheritdoc cref="DateTime.Today"/>
    public static DateTime Today
    {
        get { return _getTime.Value().Date; }
    }

    /// <inheritdoc cref="DateTime.Now"/>
    public static DateTime Now
    {
        get { return _getTime.Value(); }
    }

    /// <inheritdoc cref="DateTime.UtcNow"/>
    public static DateTime UtcNow
    {
        get { return _getTime.Value().ToUniversalTime(); }
    }

    /// <summary>
    /// Sets a fixed (deterministic) time for the current thread to return by <see cref="SystemClock"/>.
    /// </summary>
    public static void Set(DateTime time)
    {
        if (time.Kind != DateTimeKind.Local)
            time = time.ToLocalTime();

        _getTime.Value = () => time;
    }

    /// <summary>
    /// Resets <see cref="SystemClock"/> to return the current <see cref="DateTime.Now"/>.
    /// </summary>
    public static void Reset()
    {
        _getTime.Value = () => DateTime.Now;
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

[TestMethod]
public void Today()
{
    SystemClock.Set(new DateTime(2015, 4, 3));

    DateTime expectedDay = new DateTime(2015, 4, 2);
    DateTime yesterday = SystemClock.Today.AddDays(-1D);
    Assert.AreEqual(expectedDay, yesterday);

    SystemClock.Reset();
}
Run Code Online (Sandbox Code Playgroud)