我有一些单元测试,期望“当前时间”与DateTime不同。显然,我不想改变电脑的时间。
实现这一目标的最佳策略是什么?
我有一些单元测试,期望“当前时间”与DateTime不同。显然,我不想改变电脑的时间。
实现这一目标的最佳策略是什么?
当前回答
为系统添加一个假程序集(右键单击System reference=>添加假程序集)。
并在测试方法中写入:
using (ShimsContext.Create())
{
System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10);
MethodThatUsesDateTimeNow();
}
其他回答
一个老问题,但仍然有效。
我的方法是创建一个新的接口和类来包装System.DateTime.Now调用
public interface INow
{
DateTime Execute();
}
public sealed class Now : INow
{
public DateTime Execute()
{
return DateTime.Now
}
}
该接口可以注入到任何需要获取当前日期和时间的类中。在这个例子中,我有一个类,它将一个时间跨度添加到当前日期和时间(一个可测试的单元System.DateTime.Now.Add(timespan))
public interface IAddTimeSpanToCurrentDateAndTime
{
DateTime Execute(TimeSpan input);
}
public class AddTimeSpanToCurrentDateAndTime : IAddTimeSpanToCurrentDateAndTime
{
private readonly INow _now;
public AddTimeSpanToCurrentDateAndTime(INow now)
{
this._now = now;
}
public DateTime Execute(TimeSpan input)
{
var currentDateAndTime = this._now.Execute();
return currentDateAndTime.Add(input);
}
}
并且可以编写测试以确保其正确运行。我使用NUnit和Moq,但任何测试框架都可以
public class Execute
{
private Moq.Mock<INow> _nowMock;
private AddTimeSpanToCurrentDateAndTime _systemUnderTest;
[SetUp]
public void Initialize()
{
this._nowMock = new Moq.Mock<INow>(Moq.MockBehavior.Strict);
this._systemUnderTest = AddTimeSpanToCurrentDateAndTime(
this._nowMock.Object);
}
[Test]
public void AddTimeSpanToCurrentDateAndTimeExecute0001()
{
// arrange
var input = new TimeSpan(911252);
// arrange : mocks
this._nowMock
.Setup(a => a.Execute())
.Returns(new DateTime(348756););
// arrange : expected
var expected = new DateTime(911252 + 348756);
// act
var actual = this._systemUnderTest.Execute(input).Result;
// assert
Assert.Equals(actual, expected);
}
}
此模式适用于依赖于外部因素的任何函数,如System.Random.Next(), System.DateTime.Now。UtcNow, system . guide . newguid()等。
参见https://loadlimited.visualstudio.com/Stamina/_git/Stamina.Core获取更多示例或获取https://www.nuget.org/packages/Stamina.Core nuget包。
我很惊讶没有人提出一个最明显的方法:
public class TimeDependentClass
{
public void TimeDependentMethod(DateTime someTime)
{
if (GetCurrentTime() > someTime) DoSomething();
}
protected virtual DateTime GetCurrentTime()
{
return DateTime.Now; // or UtcNow
}
}
然后,您可以简单地在测试double中重写此方法。
在某些情况下,我还喜欢注入一个TimeProvider类,但对于其他情况,这就足够了。不过,如果需要在多个类中重用TimeProvider版本,我可能更喜欢它。
编辑:对于任何感兴趣的人来说,这被称为向类中添加“接缝”,在这个点上,您可以钩入它的行为来修改它(用于测试目的或其他),而无需实际更改类中的代码。
关于模拟DateTime有一个特别的注意事项。现在使用TypeMock…
DateTime的值。现在必须放置到一个变量中,以便正确地模拟。例如:
这行不通:
if ((DateTime.Now - message.TimeOpened.Value) > new TimeSpan(1, 0, 0))
然而,这样做:
var currentDateTime = DateTime.Now;
if ((currentDateTime - message.TimeOpened.Value) > new TimeSpan(1, 0, 0))
您可以更改您正在测试的类使用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();
}
}
也许不太专业,但更简单的解决方案可以在消费者方法中创建一个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)
{
}
}