Модульное Тестирование: DateTime.Сейчас
У меня есть некоторые модульные тесты, которые ожидают, что "текущее время" будет отличаться от DateTime.Теперь и я не хочу менять время компьютера, очевидно.
какова лучшая стратегия для достижения этой цели?
20 ответов:
The лучшие стратегия оберните текущее время в абстракцию и введите эту абстракцию в потребитель.
как вариант, вы также можете определить абстракцию время как Контексте Окружающей Среды:
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.Current
С тестовым двойным / макетным объектом. Пример использования Moq:var timeMock = new Mock<TimeProvider>(); timeMock.SetupGet(tp => tp.UtcNow).Returns(new DateTime(2010, 3, 11)); TimeProvider.Current = timeMock.Object;
однако, при модульном тестировании со статическим состоянием, всегда помните снесите ваше приспособление по телефону
TimeProvider.ResetToDefault()
.
это все хорошие ответы, это то, что я сделал на другом проекте:
использование:
получить сегодняшнее реальное время даты
var today = SystemTime.Now().Date;
вместо того, чтобы использовать тип datetime.Теперь, вам нужно использовать
SystemTime.Now()
... Это не трудно изменить, но это решение не может быть идеальным для всех проектов.путешествие во времени (отпустит 5 лет в будущем)
SystemTime.SetDateTime(today.AddYears(5));
Получить Нашу Подделку "сегодня" (будет 5 лет от "Сегодня")
var fakeToday = SystemTime.Now().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 }
отказ от ответственности - я работаю на родинки
у вас есть несколько вариантов для этого:
использовать насмешливый рамок и использовать 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; } } }
подделка DateTime непосредственно с помощью изолятора:
[Test] public void TestOfDateTime() { Isolate.WhenCalled(() => DateTime.Now).WillReturn(new DateTime(2000, 1, 1)); var result = new UnderTest().CalculateSomethingBasedOnDate(); }
отказ от ответственности - я работаю в Typemock
добавить поддельную сборку для системы (щелкните правой кнопкой мыши на системной ссылке=>добавить поддельную сборку).
и напишите в свой метод тестирования:
using (ShimsContext.Create()) { System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10); MethodThatUsesDateTimeNow(); }
потокобезопасным
SystemClock
используяThreadLocal<T>
прекрасно работает для меня.
ThreadLocal<T>
доступны .Чистый рамках В4.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 'Invoke' не поддерживается в LINQ to Entities). Я изменил реализацию к этому:
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; } } }
чтобы проверить код, который зависит от
System.DateTime
наsystem.dll
нельзя смеяться.есть две структуры, которые я знаю, что делает это. Microsoft fakes и комбинезоны.
Microsoft подделки требуют visual studio 2012 ультиматум и работает прямо из Комптона.
блузы с открытым исходным кодом и очень проста в использовании. Он может быть загружен с помощью NuGet.
ниже показан макет
System.DateTime
:Smock.Run(context => { context.Setup(() => DateTime.Now).Returns(new DateTime(2000, 1, 1)); // Outputs "2000" Console.WriteLine(DateTime.Now.Year); });
я столкнулся с этой же проблемой, но нашел исследовательского проекта от Microsoft, который решает эту проблему.
http://research.microsoft.com/en-us/projects/moles/
Moles-это облегченная платформа для тестовых заготовок и обходов в .NET, основанная на делегатах. Кроты могут использоваться для обхода любого метода .NET, включая невиртуальные / статические методы в запечатанных типах
// 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!"); }
пример кода был изменен от оригинала.
У меня был сделал то, что другие предложили и абстрагировали DateTime в провайдера. Это было просто неправильно, и я чувствовал, что это было слишком много только для тестирования. Я собираюсь реализовать это в своем личном проекте Сегодня вечером.
Я удивлен, что никто не предложил один из самых очевидных способов пойти:
public class TimeDependentClass { public void TimeDependentMethod(DateTime someTime) { if (GetCurrentTime() > someTime) DoSomething(); } protected virtual DateTime GetCurrentTime() { return DateTime.Now; // or UtcNow } }
затем вы можете просто переопределить этот метод в своем тест двойной.
Я также вроде как инъекции
TimeProvider
класс В некоторых случаях, но для других, этого более чем достаточно. Я бы, наверное, предпочелTimeProvider
версия, Если вам нужно повторно использовать это в нескольких классах, хотя.EDIT: для всех заинтересованных это называется добавлением "шва" в ваш класс, точка, где вы можете подключитесь к его поведению, чтобы изменить его (для целей тестирования или иным образом) без фактического изменения кода в классе.
одна специальная заметка о насмешках
DateTime.Now
С TypeMock...значение
DateTime.Now
должен быть помещен в переменную, чтобы это было высмеяно должным образом. Например:это не работает:
, это: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))
хорошая практика, когда 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; } }
Далее, вы можете ввести свой поддельный DateTime для модульных тестов
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
я получил ту же проблему, но я думал, что мы не должны использовать набор datetime вещи на том же классе. потому что однажды это может привести к неправильному использованию. поэтому я использовал провайдера, как
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() { } }
для тестов, в тестовом проекте сделан помощник, который будет заниматься заданными вещами,
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
а на тестах
[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). Но в большинстве случаев это еще меньше
найти выборку в MockSamples
альтернативный вариант, который не упоминается, заключается в том, чтобы ввести текущее время в зависимый метод:
public class DateTimeNowDependencyClass { ... public void ImplicitTimeDependencyMethod(Obj arg0) { this.TimeDependencyMethod(DateTime.Now, arg0); } internal void TimeDependencyMethod(DateTime now, Obj arg1) { ... } ... }
внутренняя вариация открыта для модульного тестирования, параллельного или нет.
это основано на принципе, согласно которому
ImplicitTimeDependencyMethod
" слишком просто сломать "(см.:http://junit.sourceforge.net/doc/faq/faq.htm#best_3) так не нужно быть включенным в охвате модульного теста. Хотя это надо трогать в интеграционных тестах в любом случае.в зависимости от цели класса может быть желательно, чтобы оба эти метода были общедоступными в любом случае.
вот мой ответ на этот вопрос. Я объединяю шаблон "окружающий контекст" с 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 внутри Unit-Test
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
мы были вынуждены принять его в специальные общие проект, на который необходимо ссылаться из остальных других проектов. Но это сложный контроль зависимостей.искали
ITimeProvider
в .NET framework. Мы искали пакет 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()); } }
Как зарегистрировать
Autofac
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 , сделать 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
что позволяет, например, сделать время, которое движется в 5 раз быстрее на всех вызовах DateTime.Сейчас или 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)); }
вы можете проверить больше тестов здесь:
Какая Дата И Время.Сейчас.Расширение ToVirtualTime дает вам экземпляр ITime, который вы передаете методу / классу, который зависит от ITime. некоторые даты.Сейчас.ToVirtualTime настраивается в контейнере DI по вашему выбору
вот еще один пример инъекции в a класс contrustor
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); }
мы использовали статический объект SystemTime, но столкнулись с проблемами при выполнении параллельных модульных тестов. Я попытался использовать решение Henk van Boeijen, но имел проблемы с порожденными асинхронными потоками, в конечном итоге используя использование AsyncLocal способом, подобным приведенному ниже:
public static class Clock { private static Func<DateTime> _utcNow = () => DateTime.UtcNow; static AsyncLocal<Func<DateTime>> _override = new AsyncLocal<Func<DateTime>>(); public static DateTime UtcNow => (_override.Value ?? _utcNow)(); public static void Set(Func<DateTime> func) { _override.Value = func; } public static void Reset() { _override.Value = null; } }
получены из https://gist.github.com/CraftyFella/42f459f7687b0b8b268fc311e6b4af08