我有一些单元测试,期望“当前时间”与DateTime不同。显然,我不想改变电脑的时间。
实现这一目标的最佳策略是什么?
我有一些单元测试,期望“当前时间”与DateTime不同。显然,我不想改变电脑的时间。
实现这一目标的最佳策略是什么?
当前回答
我很惊讶没有人提出一个最明显的方法:
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参数。例如,不使用像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)
{
}
}
一个老问题,但仍然有效。
我的方法是创建一个新的接口和类来包装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 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
}
免责声明-我工作的鼹鼠
你有一些选择:
使用模拟框架并使用DateTimeService(实现一个小型包装器类并将其注入到生产代码中)。包装器实现将访问DateTime,在测试中您将能够模拟包装器类。 使用Typemock隔离器,它可以伪造日期时间。现在,并且不需要您更改测试中的代码。 使用鼹鼠,它也可以伪造日期时间。现在,并且不需要更改生产代码。
一些例子:
使用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; } }
}
直接使用隔离器伪造日期时间:
[Test]
public void TestOfDateTime()
{
Isolate.WhenCalled(() => DateTime.Now).WillReturn(new DateTime(2000, 1, 1));
var result = new UnderTest().CalculateSomethingBasedOnDate();
}
免责声明-我在Typemock工作