У меня есть некоторые модульные тесты, которые ожидают, что "текущее время" будет отличаться от DateTime. Теперь и я не хочу менять время компьютера, очевидно.
Какая лучшая стратегия для достижения этой цели?
У меня есть некоторые модульные тесты, которые ожидают, что "текущее время" будет отличаться от DateTime. Теперь и я не хочу менять время компьютера, очевидно.
Какая лучшая стратегия для достижения этой цели?
Стратегия лучше заключается в обернуть текущее время в абстракции и вставить эту абстракцию в пользователя.
В качестве альтернативы вы также можете определить абстракцию времени как Окружающий контекст:
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 test вы можете заменить TimeProvider.Current на объект 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().
Все это хорошие ответы, вот что я сделал в другом проекте:
Применение:
Получить сегодня РЕАЛЬНАЯ дата Время
var today = SystemTime.Now().Date;
Вместо использования DateTime.Now вам нужно использовать SystemTime.Now()... Это не сложно, но это решение может быть не идеальным для всех проектов.
Путешествие во времени (продолжится через 5 лет)
SystemTime.SetDateTime(today.AddYears(5));
Получите наш фальшивый "сегодня" (будет 5 лет от "сегодня" )
var fakeToday = SystemTime.Now().Date;
Reset дата
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
}
Отказ от ответственности - я работаю на Moles
У вас есть несколько вариантов для этого:
Используйте mocking framework и используйте DateTimeService (Внесите небольшой класс оболочки и добавьте его в производственный код). Реализация оболочки будет иметь доступ к DateTime, и в тестах вы сможете издеваться над классом-оболочкой.
Используйте Typemock Isolator, он может подделка DateTime.Now и не потребует от вас изменения тестируемого кода.
Используйте Moles, он также может поддельный DateTime. Теперь и не потребует изменений в производственном коде.
Некоторые примеры:
Класс Wrapper с использованием 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; } }
}
Faking DateTime напрямую с помощью Isolator:
[Test]
public void TestOfDateTime()
{
     Isolate.WhenCalled(() => DateTime.Now).WillReturn(new DateTime(2000, 1, 1));
     var result = new UnderTest().CalculateSomethingBasedOnDate();
}
Отказ от ответственности - я работаю на Typemock
Добавьте поддельную сборку для системы (щелкните правой кнопкой мыши по ссылке System = > Добавить поддельную сборку).
И напишите в свой тестовый метод:
using (ShimsContext.Create())
{
   System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10);
   MethodThatUsesDateTimeNow();
}
		  Безопасный поток SystemClock с помощью ThreadLocal<T> отлично работает для меня.
 ThreadLocal<T> доступен в .NET Framework версии 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 node type '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;
            }
        }
    }
		Макет объектов.
Макет DateTime, который возвращает значение Now, соответствующее вашему тесту.
Одно замечание о насмешливости 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))
		Я столкнулся с этой проблемой, но нашел исследовательский проект от Microsoft, который решает эту проблему.
http://research.microsoft.com/en-us/projects/moles/
Moles - это легкая структура для тестовых заглушек и обходов в .NET, основанная на делегатах. Моли могут использоваться для обхода любого метода .NET, включая не виртуальные/статические методы в закрытых типах.
// Let 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: для всех, кого это интересует, это называется добавлением "шва" к вашему классу, в котором вы можете подключиться к нему, чтобы изменить его (для целей тестирования или иначе), без необходимости изменять код в класс.
Чтобы проверить код, который зависит от System.DateTime, system.dll должен быть изделен.
Есть две рамки, которые я знаю об этом. Microsoft подделывает и Smocks.
Подделки Microsoft требуют ультиматума visual studio 2012 и работают прямо из компромета.
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; 
    } 
} 
Затем вы можете ввести свой фальшивый 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), поэтому не нужно для включения в покрытие unit test. Хотя в любом случае это должно быть затронуто интеграционными испытаниями.
В зависимости от цели класса может быть желательно, чтобы оба этих метода были общедоступными.
Вот мой вопрос к этому вопросу. Я совмещаю шаблон "Окружающий контекст" с IDisposable. Таким образом, вы можете использовать DateTimeProvider.Current в своем обычном программном коде, а в тесте вы переопределяете область действия с помощью оператора 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 на потребительском методе. Например, вместо make-метода, такого как SampleMethod, 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
 Это позволяет, например, совершать время, которое перемещается в 5 раз быстрее при всех вызовах DateTime.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));
}
 Вы можете проверить больше тестов здесь:
Расширение DateTime.Now.ToVirtualTime дает вам экземпляр ITime, который вы передаете методу/классу, который зависит от ITime. некоторые DateTime.Now.ToVirtualTime настраиваются в контейнере DI по вашему выбору
Вот еще один пример, вводящий в класс 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);
}