我有一些单元测试,期望“当前时间”与DateTime不同。显然,我不想改变电脑的时间。
实现这一目标的最佳策略是什么?
我有一些单元测试,期望“当前时间”与DateTime不同。显然,我不想改变电脑的时间。
实现这一目标的最佳策略是什么?
当前回答
您可以更改您正在测试的类使用Func<DateTime>,它将通过它的构造函数参数传递,因此当您在实际代码中创建类的实例时,您可以pass () => 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();
}
}
其他回答
最好的策略是将当前时间封装在一个抽象中,并将该抽象注入到使用者中。
或者,你也可以将时间抽象定义为环境上下文:
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;
}
}
这将使你能够像这样消费它:
var now = TimeProvider.Current.UtcNow;
在单元测试中,您可以替换TimeProvider。当前带有Test Double/Mock对象。使用Moq的例子:
var timeMock = new Mock<TimeProvider>();
timeMock.SetupGet(tp => tp.UtcNow).Returns(new DateTime(2010, 3, 11));
TimeProvider.Current = timeMock.Object;
但是,当使用静态状态进行单元测试时,始终记得通过调用TimeProvider.ResetToDefault()来删除fixture。
摩尔数:
[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
}
免责声明-我工作的鼹鼠
测试依赖于系统的代码。DateTime,系统.dll必须被模拟。
我知道有两个框架可以做到这一点。微软的假货和罩衫。
微软的假货需要visual studio 2012的最后通牒,直接从康普顿出来。
Smocks是一个开放源代码,非常容易使用。可以使用NuGet下载。
下面是System的一个模拟。DateTime:
Smock.Run(context =>
{
context.Setup(() => DateTime.Now).Returns(new DateTime(2000, 1, 1));
// Outputs "2000"
Console.WriteLine(DateTime.Now.Year);
});
一种简单的方法是注入VirtualTime。它可以让你控制时间。 首先安装VirtualTime
Install-Package VirtualTime
例如,这允许在所有对DateTime的调用上使时间加快5倍。Now或UtcNow
var DateTime = DateTime.Now.ToVirtualTime(5);
放慢时间(如放慢5倍)
var DateTime = DateTime.Now.ToVirtualTime(0.5);
让时间静止
var DateTime = DateTime.Now.ToVirtualTime(0);
回到过去还没有测试过
下面是一个测试示例:
[TestMethod]
public void it_should_make_time_move_faster()
{
int speedOfTimePerMs = 1000;
int timeToPassMs = 3000;
int expectedElapsedVirtualTime = speedOfTimePerMs * timeToPassMs;
DateTime whenTimeStarts = DateTime.Now;
ITime time = whenTimeStarts.ToVirtualTime(speedOfTimePerMs);
Thread.Sleep(timeToPassMs);
DateTime expectedTime = DateTime.Now.AddMilliseconds(expectedElapsedVirtualTime - timeToPassMs);
DateTime virtualTime = time.Now;
Assert.IsTrue(TestHelper.AreEqualWithinMarginOfError(expectedTime, virtualTime, MarginOfErrorMs));
}
你可以在这里查看更多测试:
https://github.com/VirtualTime/VirtualTime/blob/master/VirtualTimeLib.Tests/when_virtual_time_is_used.cs
什么DateTime.Now.ToVirtualTime扩展给你是一个ITime的实例,你传递给一个依赖于ITime的方法/类。一些DateTime.Now.ToVirtualTime被设置在你选择的DI容器中
下面是注入类构造函数的另一个例子
public class AlarmClock
{
private ITime DateTime;
public AlarmClock(ITime dateTime, int numberOfHours)
{
DateTime = dateTime;
SetTime = DateTime.UtcNow.AddHours(numberOfHours);
Task.Run(() =>
{
while (!IsAlarmOn)
{
IsAlarmOn = (SetTime - DateTime.UtcNow).TotalMilliseconds < 0;
}
});
}
public DateTime SetTime { get; set; }
public bool IsAlarmOn { get; set; }
}
[TestMethod]
public void it_can_be_injected_as_a_dependency()
{
//virtual time has to be 1000*3.75 faster to get to an hour
//in 1000 ms real time
var dateTime = DateTime.Now.ToVirtualTime(1000 * 3.75);
var numberOfHoursBeforeAlarmSounds = 1;
var alarmClock = new AlarmClock(dateTime, numberOfHoursBeforeAlarmSounds);
Assert.IsFalse(alarmClock.IsAlarmOn);
System.Threading.Thread.Sleep(1000);
Assert.IsTrue(alarmClock.IsAlarmOn);
}
以下是我对这个问题的回答。我将“环境上下文”模式与IDisposable结合起来。你可以使用DateTimeProvider。当前在您的正常程序代码和测试中,您使用using语句覆盖范围。
using System;
using System.Collections.Immutable;
namespace ambientcontext {
public abstract class DateTimeProvider : IDisposable
{
private static ImmutableStack<DateTimeProvider> stack = ImmutableStack<DateTimeProvider>.Empty.Push(new DefaultDateTimeProvider());
protected DateTimeProvider()
{
if (this.GetType() != typeof(DefaultDateTimeProvider))
stack = stack.Push(this);
}
public static DateTimeProvider Current => stack.Peek();
public abstract DateTime Today { get; }
public abstract DateTime Now {get; }
public void Dispose()
{
if (this.GetType() != typeof(DefaultDateTimeProvider))
stack = stack.Pop();
}
// Not visible Default Implementation
private class DefaultDateTimeProvider : DateTimeProvider {
public override DateTime Today => DateTime.Today;
public override DateTime Now => DateTime.Now;
}
}
}
下面是如何在单元测试中使用上述DateTimeProvider
using System;
using Xunit;
namespace ambientcontext
{
public class TestDateTimeProvider
{
[Fact]
public void TestDateTime()
{
var actual = DateTimeProvider.Current.Today;
var expected = DateTime.Today;
Assert.Equal<DateTime>(expected, actual);
using (new MyDateTimeProvider(new DateTime(2012,12,21)))
{
Assert.Equal(2012, DateTimeProvider.Current.Today.Year);
using (new MyDateTimeProvider(new DateTime(1984,4,4)))
{
Assert.Equal(1984, DateTimeProvider.Current.Today.Year);
}
Assert.Equal(2012, DateTimeProvider.Current.Today.Year);
}
// Fall-Back to Default DateTimeProvider
Assert.Equal<int>(expected.Year, DateTimeProvider.Current.Today.Year);
}
private class MyDateTimeProvider : DateTimeProvider
{
private readonly DateTime dateTime;
public MyDateTimeProvider(DateTime dateTime):base()
{
this.dateTime = dateTime;
}
public override DateTime Today => this.dateTime.Date;
public override DateTime Now => this.dateTime;
}
}
}