我有一些单元测试,期望“当前时间”与DateTime不同。显然,我不想改变电脑的时间。
实现这一目标的最佳策略是什么?
我有一些单元测试,期望“当前时间”与DateTime不同。显然,我不想改变电脑的时间。
实现这一目标的最佳策略是什么?
当前回答
在使用ITimeProvider时,我们被迫将它作为一个特殊的共享项目,必须从其他项目中引用它。但这使得依赖关系的控制变得复杂。
我们在。net框架中搜索ITimeProvider。我们搜索了NuGet包,发现了一个不能使用DateTimeOffset的包。
所以我们提出了自己的解决方案,它只依赖于标准库的类型。我们正在使用Func<DateTimeOffset>的实例。
如何使用
public class ThingThatNeedsTimeProvider
{
private readonly Func<DateTimeOffset> now;
private int nextId;
public ThingThatNeedsTimeProvider(Func<DateTimeOffset> now)
{
this.now = now;
this.nextId = 1;
}
public (int Id, DateTimeOffset CreatedAt) MakeIllustratingTuple()
{
return (nextId++, now());
}
}
如何注册
自发法克
builder.RegisterInstance<Func<DateTimeOffset>>(() => DateTimeOffset.Now);
(未来的编辑请在这里附上你的案例)。
如何进行单元测试
public void MakeIllustratingTuple_WhenCalled_FillsCreatedAt()
{
DateTimeOffset expected = CreateRandomDateTimeOffset();
DateTimeOffset StubNow() => expected;
var thing = new ThingThatNeedsTimeProvider(StubNow);
var (_, actual) = thing.MakeIllustratingTuple();
Assert.AreEqual(expected, actual);
}
其他回答
摩尔数:
[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
}
免责声明-我工作的鼹鼠
在使用ITimeProvider时,我们被迫将它作为一个特殊的共享项目,必须从其他项目中引用它。但这使得依赖关系的控制变得复杂。
我们在。net框架中搜索ITimeProvider。我们搜索了NuGet包,发现了一个不能使用DateTimeOffset的包。
所以我们提出了自己的解决方案,它只依赖于标准库的类型。我们正在使用Func<DateTimeOffset>的实例。
如何使用
public class ThingThatNeedsTimeProvider
{
private readonly Func<DateTimeOffset> now;
private int nextId;
public ThingThatNeedsTimeProvider(Func<DateTimeOffset> now)
{
this.now = now;
this.nextId = 1;
}
public (int Id, DateTimeOffset CreatedAt) MakeIllustratingTuple()
{
return (nextId++, now());
}
}
如何注册
自发法克
builder.RegisterInstance<Func<DateTimeOffset>>(() => DateTimeOffset.Now);
(未来的编辑请在这里附上你的案例)。
如何进行单元测试
public void MakeIllustratingTuple_WhenCalled_FillsCreatedAt()
{
DateTimeOffset expected = CreateRandomDateTimeOffset();
DateTimeOffset StubNow() => expected;
var thing = new ThingThatNeedsTimeProvider(StubNow);
var (_, actual) = thing.MakeIllustratingTuple();
Assert.AreEqual(expected, actual);
}
也许不太专业,但更简单的解决方案可以在消费者方法中创建一个DateTime参数。例如,不使用像SampleMethod这样的make方法,而是使用带参数的make SampleMethod1。SampleMethod1的测试更简单
public void SampleMethod()
{
DateTime anotherDateTime = DateTime.Today.AddDays(-10);
if ((DateTime.Now-anotherDateTime).TotalDays>10)
{
}
}
public void SampleMethod1(DateTime dateTimeNow)
{
DateTime anotherDateTime = DateTime.Today.AddDays(-10);
if ((dateTimeNow - anotherDateTime).TotalDays > 10)
{
}
}
一种简单的方法是注入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;
}
}
}