unit testing:DateTime.Now
我有一些unit testing,预计“当前时间”不同于DateTime.Now,我不想改变计算机的时间,显然。 什么是实现这一目标的最佳策略?
谢谢
最好的策略是把当前的时间包装在一个抽象中,并将抽象注入消费者 。
或者 ,您也可以将时间抽象定义为环境上下文 :
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;
在unit testing中,可以用一个testing双/模拟对象replaceTimeProvider.Current
。 使用Moq的例子:
var timeMock = new Mock<TimeProvider>(); timeMock.SetupGet(tp => tp.UtcNow).Returns(new DateTime(2010, 3, 11)); TimeProvider.Current = timeMock.Object;
但是,当使用静态状态进行unit testing时,请务必通过调用TimeProvider.ResetToDefault()
来logging您的设备 。
这些都是很好的答案,这是我在一个不同的项目上做的:
用法:
获取今天的实时date
var today = SystemTime.Now().Date;
而不是使用DateTime.Now,你需要使用SystemTime.Now()
…这并不难,但这个解决scheme可能不是所有项目的理想select。
时间旅行(让未来5年)
SystemTime.SetDateTime(today.AddYears(5));
“今天得到我们的假”(将从“今天”起5年)
var fakeToday = SystemTime.Now().Date;
重置date
SystemTime.ResetDateTime();
/// <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; } }
摩尔:
[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 }
免责声明 – 我工作在痣
你有这样做的一些select:
-
使用模拟框架并使用DateTimeService(实现一个小的包装类,并将其注入到生产代码中)。 包装器实现将访问DateTime,在testing中,你将能够模拟包装类。
-
使用Typemock隔离器 ,它可以伪造DateTime.Now ,不会要求您更改testing中的代码。
-
使用痣 ,它也可以伪造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; } } }
使用隔离器直接伪造DateTime:
[Test] public void TestOfDateTime() { Isolate.WhenCalled(() => DateTime.Now).WillReturn(new DateTime(2000, 1, 1)); var result = new UnderTest().CalculateSomethingBasedOnDate(); }
免责声明 – 我在Typemock工作
为系统添加一个伪装(右键单击系统参考=>添加伪assembly)。
并写入您的testing方法:
using (ShimsContext.Create()) { System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10); MethodThatUsesDateTimeNow(); }
使用ThreadLocal<T>
线程安全的SystemClock
对我来说非常好。
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; } }
用法示例:
[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(); }
关于@crabcrusherclamcollector答案在EF查询中使用该方法时存在问题(System.NotSupportedException:LINQ to Entities不支持LINQexpression式节点types“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; } } }
模拟对象。
一个模拟的DateTime,返回一个适合你的testing的Now。
关于用TypeMock嘲笑DateTime.Now的一个特别说明…
DateTime.Now的值必须放在一个variables中,以便正确模拟。 例如:
这不起作用:
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))
我遇到了同样的问题,但发现了微软的一个研究项目,解决了这个问题。
http://research.microsoft.com/en-us/projects/moles/
Moles是基于代表的.NET中testing存根和绕行的轻量级框架。 痣可以用来绕开任何.NET方法,包括密封types的非虚拟/静态方法
// Let's detour DateTime.Now MDateTime.NowGet = () => new DateTime(2000,1, 1); if (DateTime.Now == new DateTime(2000, 1, 1); { throw new Exception("Wahoo we did it!"); }
示例代码是从原来的修改。
我完成了其他build议,并将DateTime抽象为提供者。 它只是感觉错了,我觉得它太多只是为了testing。 我今天晚上将把这个实现到我的个人项目中。
我很惊讶没有人提出最明显的方法之一:
public class TimeDependentClass { public void TimeDependentMethod(DateTime someTime) { if (GetCurrentTime() > someTime) DoSomething(); } protected virtual DateTime GetCurrentTime() { return DateTime.Now; // or UtcNow } }
那么你可以简单地在你的testing中重写这个方法。
我也有点喜欢在某些情况下注入TimeProvider
类,但对于其他人来说,这已经足够了。 我可能会喜欢TimeProvider
版本,如果你需要在几个类中重用这个。
编辑:对于任何感兴趣的人来说,这就是所谓的添加一个“接缝”到你的类,一个点,你可以挂钩到它的行为来修改它(用于testing目的或其他),而实际上不必更改类中的代码。
要testing依赖于System.DateTime
的代码,必须system.dll
。
有两个我知道的框架是这样做的。 微软的假货和工作服 。
微软的假货需要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); });
良好的做法是,当DateTimeProvider实现IDisposable。
public class DateTimeProvider : IDisposable { [ThreadStatic] private static DateTime? _injectedDateTime; private DateTimeProvider() { } /// <summary> /// Gets DateTime now. /// </summary> /// <value> /// The DateTime now. /// </value> public static DateTime Now { get { return _injectedDateTime ?? DateTime.Now; } } /// <summary> /// Injects the actual date time. /// </summary> /// <param name="actualDateTime">The actual date time.</param> public static IDisposable InjectActualDateTime(DateTime actualDateTime) { _injectedDateTime = actualDateTime; return new DateTimeProvider(); } public void Dispose() { _injectedDateTime = null; } }
接下来,你可以注入你的假DateTimeunit testing
using (var date = DateTimeProvider.InjectActualDateTime(expectedDateTime)) { var bankAccount = new BankAccount(); bankAccount.DepositMoney(600); var lastTransaction = bankAccount.Transactions.Last(); Assert.IsTrue(expectedDateTime.Equals(bankAccount.Transactions[0].TransactionDate)); }
请参阅DateTimeProvider的示例示例
我有同样的问题,但我想我们不应该使用同一类的设置date时间的东西。 因为有一天可能会导致误用。 所以我使用了提供者
public class DateTimeProvider { protected static DateTime? DateTimeNow; protected static DateTime? DateTimeUtcNow; public DateTime Now { get { return DateTimeNow ?? System.DateTime.Now; } } public DateTime UtcNow { get { return DateTimeUtcNow ?? System.DateTime.UtcNow; } } public static DateTimeProvider DateTime { get { return new DateTimeProvider(); } } protected DateTimeProvider() { } }
对于testing,在testing项目上做了一个帮手,
public class MockDateTimeProvider : DateTimeProvider { public static void SetNow(DateTime now) { DateTimeNow = now; } public static void SetUtcNow(DateTime utc) { DateTimeUtcNow = utc; } public static void RestoreAsDefault() { DateTimeNow = null; DateTimeUtcNow = null; } }
在代码上
var dateTimeNow = DateTimeProvider.DateTime.Now //not DateTime.Now var dateTimeUtcNow = DateTimeProvider.DateTime.UtcNow //not DateTime.UtcNow
并在testing
[Test] public void Mocked_Now() { DateTime now = DateTime.Now; MockDateTimeProvider.SetNow(now); //set to mock Assert.AreEqual(now, DateTimeProvider.DateTime.Now); Assert.AreNotEqual(now, DateTimeProvider.DateTime.UtcNow); } [Test] public void Mocked_UtcNow() { DateTime utcNow = DateTime.UtcNow; MockDateTimeProvider.SetUtcNow(utcNow); //set to mock Assert.AreEqual(utcNow, DateTimeProvider.DateTime.UtcNow); Assert.AreNotEqual(utcNow, DateTimeProvider.DateTime.Now); }
但是需要记住一件事,有时候真正的DateTime和提供者的DateTime不会相同
[Test] public void Now() { Assert.AreEqual(DateTime.Now.Kind, DateTimeProvider.DateTime.Now.Kind); Assert.LessOrEqual(DateTime.Now, DateTimeProvider.DateTime.Now); Assert.LessOrEqual(DateTimeProvider.DateTime.Now - DateTime.Now, TimeSpan.FromMilliseconds(1)); }
我认为尊重将是最大TimeSpan.FromMilliseconds(0.00002) 。 但是大部分时间更less
在MockSamples中find示例
没有提到的替代选项是将当前时间注入依赖方法:
public class DateTimeNowDependencyClass { ... public void ImplicitTimeDependencyMethod(Obj arg0) { this.TimeDependencyMethod(DateTime.Now, arg0); } internal void TimeDependencyMethod(DateTime now, Obj arg1) { ... } ... }
内部变化是开放的unit testing,平行与否。
这是基于ImplicitTimeDependencyMethod
“太简单不能打破”的原则(见http://junit.sourceforge.net/doc/faq/faq.htm#best_3 ),因此不需要包含在unit testing覆盖范围内。 尽pipe无论如何它应该在集成testing中被触及。
根据class级的目的,可能需要公开这两种方法。
这是我的这个问题。 我将“环境上下文”模式与IDisposable结合起来。 所以你可以在你的普通程序代码中使用DateTimeProvider.Current,在testing中用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; } } }
这里是如何在unit testing中使用上面的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; } } }
使用ITimeProvider
我们不得不把它带入特殊的共享项目,这个项目必须从其他项目中引用。 但是这会使依赖关系的控制变得复杂 。
我们在.NET框架中search了ITimeProvider
。 我们search了NuGet包,发现一个不能用DateTimeOffset
工作。
所以我们想出了自己的解决scheme,这个解决scheme只依赖于标准库的types。 我们正在使用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()); } }
如何注册
Autofac
builder.RegisterInstance<Func<DateTimeOffset>>(() => DateTimeOffset.Now);
( 对于未来的编辑:在这里追加你的个案 )。
如何进行unit testing
public void MakeIllustratingTuple_WhenCalled_FillsCreatedAt() { DateTimeOffset expected = CreateRandomDateTimeOffset(); DateTimeOffset StubNow() => expected; var thing = new ThingThatNeedsTimeProvider(StubNow); var (_, actual) = thing.MakeIllustratingTuple(); Assert.AreEqual(expected, actual); }
也许不那么专业,但更简单的解决scheme可以在消费者的方法做一个DateTime参数。例如,而不是像SampleMethod的make方法,使SampleMethod1带有参数。SampleMethod1的testing更容易
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) { } }