Модульное тестирование: DateTime.Now - PullRequest
137 голосов
/ 11 марта 2010

У меня есть некоторые модульные тесты, которые ожидают, что «текущее время» будет отличаться от DateTime.Now, и я, очевидно, не хочу изменять время компьютера.

Какова лучшая стратегия для достижения этой цели?

Ответы [ 20 ]

190 голосов
/ 11 марта 2010

Стратегия best состоит в том, чтобы обернуть текущее время в абстракцию и ввести эту абстракцию в потребителя .


В качестве альтернативы , вы также можете определить абстракцию времени как Окружающий контекст :

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 на объект 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().

52 голосов
/ 28 марта 2012

Это все хорошие ответы, это то, что я сделал в другом проекте:

Использование:

Получить РЕАЛЬНУЮ сегодняшнюю дату Время

var today = SystemTime.Now().Date;

Вместо использования DateTime.Now, вам нужно использовать 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;
    }
}
24 голосов
/ 20 марта 2010

Родинки:

[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
}

Отказ от ответственности - я работаю над родинками

16 голосов
/ 11 марта 2010

У вас есть несколько вариантов:

  1. Использовать среду разработки и использовать DateTimeService (реализовать небольшой класс-оболочку и внедрить его в производственный код). Реализация оболочки будет иметь доступ к DateTime, и в тестах вы сможете смоделировать класс оболочки.

  2. Используйте Typemock Isolator , он может подделать DateTime.Now и не потребует изменения тестируемого кода.

  3. Используйте Родинок , он также может подделать DateTime.Now и не потребует изменения в производственном коде.

Некоторые примеры:

Класс 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; } }
}

Подделка DateTime напрямую с использованием Isolator:

[Test]
public void TestOfDateTime()
{
     Isolate.WhenCalled(() => DateTime.Now).WillReturn(new DateTime(2000, 1, 1));

     var result = new UnderTest().CalculateSomethingBasedOnDate();
}

Отказ от ответственности - я работаю в Typemock

10 голосов
/ 10 марта 2014

Добавление поддельной сборки для системы (щелкните правой кнопкой мыши ссылку на систему => Добавить поддельную сборку).

И напиши в свой метод испытаний:

using (ShimsContext.Create())
{
   System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10);
   MethodThatUsesDateTimeNow();
}
6 голосов
/ 11 сентября 2015

Относительно ответа @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;
            }
        }
    }
5 голосов
/ 03 апреля 2015

Поточно-безопасный 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();
}
3 голосов
/ 06 апреля 2016

Для проверки кода, который зависит от System.DateTime, system.dll должен быть проверен.

Есть две основы, о которых я знаю, которые делают это. Microsoft подделывает и Халаты .

Подделки 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);
});
3 голосов
/ 11 сентября 2013

Я столкнулся с той же проблемой, но нашел исследовательский проект от 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 в поставщика. Это было просто неправильно, и я чувствовал, что это слишком много только для тестирования. Я собираюсь реализовать это в моем личном проекте сегодня вечером.

2 голосов
/ 30 января 2012

Одна особая заметка о насмешках 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))
...