Модульное тестирование: DateTime.Now


164

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

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


сохраните какое-то старое значение в текущем времени и сравните его с datetime.now, поскольку модульное тестирование использует поддельные данные, вы можете легко это сделать, не так ли?
Прашант Лахлани

3
Предоставление абстракции текущего DateTime полезно не только для тестирования и отладки, но и в рабочем коде - это зависит от потребностей приложения.
Павел Ходек

1
Не используйте статический класс, чтобы получить DateTime
Даниэль Литтл

и еще один простой подход с использованием VirtualTime ниже
Сэмюэль

Один вариант, который может сработать, и вы можете добавить перегрузку, - это создать перегрузку метода, который принимает DateTime. Это будет тот, который вы тестируете, существующий метод просто вызовет перегрузку now.
Фил Купер

Ответы:


219

Лучшая стратегия заключается в завернуть текущее время в абстракции и привнести что абстракция в потребитель .


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

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().


7
В случае окружающего контекста, даже если вы не работаете, как вы выполняете свои тесты параллельно?
Илья Черномордик

2
@IlyaChernomordik Вам не нужно вводить ILogger или другие сквозные проблемы , поэтому использование поставщика времени по-прежнему является лучшим вариантом.
Марк Симанн

5
@MikeK Как сказано в первом предложении моего ответа: лучшая стратегия - обернуть текущее время в абстракцию и внедрить эту абстракцию в потребителя. Все остальное в этом ответе - вторая лучшая альтернатива.
Марк Симанн

3
Здравствуй. Я нуб. Как предотвратить доступ людей к DateTime.UtcNow с помощью этого решения? Для кого-то было бы легко пропустить использование TimeProvider. Таким образом, приводит к ошибкам в тестировании?
Obbles

1
Обзоры кода @Obbles (или парное программирование, которое является формой рецензирования кода в реальном времени). Я полагаю, можно было бы написать инструмент, который сканирует базу кода DateTime.UtcNowи тому подобное, но обзоры кода в любом случае хорошая идея.
Марк Симанн

58

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

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

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

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

1
Спасибо, мне нравится этот функциональный подход. Сегодня (два года спустя) я все еще использую модифицированную версию класса TimeProvider (проверьте принятый ответ), он работает очень хорошо.
Педро

7
@crabCRUSHERclamCOLI: Если вы получили идею от ayende.com/blog/3408/dealing-with-time-in-tests , то это хорошая вещь, чтобы дать ссылку на нее.
Иоганн Герелл

3
Эта концепция также отлично подходит для насмешливой генерации Guid (общедоступная статическая функция Func <Guid> NewGuid = () => Guid.NewGuid ();
mdwhatcott

2
Вы также можете добавить этот полезный метод: public static void ResetDateTime (DateTime dateTimeNow) {var timespanDiff = TimeSpan.FromTicks (DateTime.Now.Ticks - dateTimeNow.Ticks); Now = () => DateTime.Now - timespanDiff; }
Павел Ходек

17
очень опасно. Это может повлиять на другие модульные тесты, работающие параллельно.
Амет Блаженный

24

Родинки:

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

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


1
к сожалению, не работает с nunit и resharper.
odyth

Похоже, что Moles теперь не поддерживается, его заменили на Fakes msdn.microsoft.com/en-us/library/… Без сомнения, MS прекратит это слишком рано
Данио

17

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

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

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

  3. Используйте Moles , он также может подделать 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


12

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

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

using (ShimsContext.Create())
{
   System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10);
   MethodThatUsesDateTimeNow();
}

Если у вас есть доступ к Vs Premium или Ultimate, это самый простой и быстрый способ сделать это.
Rugdr

7

Что касается ответа @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;
            }
        }
    }

Я думаю, под EF вы имеете в виду Entity Framework? Как выглядит объект, на котором вы используете SystemTime? Я предполагаю, что у вас есть ссылка на SystemTime на самом объекте, вместо этого вы должны использовать обычный объект DateTime на вашем объекте и устанавливать его с помощью SystemTime везде, где это имеет смысл в вашем приложении
crabCRUSHERclamCOLLECTOR

1
Да, я проверял это при создании запроса linq и EntityFramework6 и обращении в этом запросе UtcNow из SystemTime. Было исключение, как описано. После изменения реализации это работает хорошо.
Марцинн

Я люблю это решение! Большой!
mirind4

7

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

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

Microsoft подделывает визуальный ультиматум студии 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);
});

5

Поток безопасного SystemClockиспользования ThreadLocal<T>прекрасно работает для меня.

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();
}

1
+! Локальный поток должен избегать проблем с параллельным выполнением теста и несколькими тестами, которые могут переопределить текущий Nowэкземпляр.
Хакс,

1
Попытался использовать это, но имел проблему между потоками, поэтому пошел с похожим принципалом, но использовал AsyncLocalвместоThreadLocal
rrrr

@rrrr: можешь ли ты объяснить возникшую проблему?
Хенк ван Бойен

@HenkvanBoeijen Конечно, мы столкнулись с проблемами, когда было установлено время, а затем мы ожидали асинхронного метода. Console.WriteLine(SystemClock.Now); SystemClock.Set(new DateTime(2017, 01, 01)); Console.WriteLine(SystemClock.Now); await Task.Delay(1000); Console.WriteLine(SystemClock.Now);
рррр

@HenkvanBoeijen Я добавил ответ с тем, что я закончил, используя ссылку
rrrr

3

Я столкнулся с этой же проблемой, но нашел исследовательский проект от 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
Кстати, это работает только с VS2010. Я был расстроен, когда обнаружил, что Moles теперь является Fakes для VS2012 и доступна только для Premium и Ultimate Visual Studios. Нет подделок для VS 2012 Professional. Поэтому я никогда не использовал это на самом деле. :(
Бобби Кэннон

3

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

2

Макет объектов.

Поддельный DateTime, который возвращает Now, подходящий для вашего теста.


2

Я удивлен, что никто не предложил один из самых очевидных путей:

public class TimeDependentClass
{
    public void TimeDependentMethod(DateTime someTime)
    {
        if (GetCurrentTime() > someTime) DoSomething();
    }

    protected virtual DateTime GetCurrentTime()
    {
        return DateTime.Now; // or UtcNow
    }
}

Тогда вы можете просто переопределить этот метод в вашем двойном тесте.

Мне также нравится вводить TimeProviderкласс в некоторых случаях, но для других этого более чем достаточно. Я, вероятно, предпочел бы TimeProviderверсию, если вам нужно использовать это в нескольких классах, хотя.

РЕДАКТИРОВАТЬ: Для всех, кто заинтересован, это называется добавлением «шва» в ваш класс, точки, где вы можете подключиться к его поведению, чтобы изменить его (для целей тестирования или иным образом) без необходимости фактически изменять код в классе.


1

Хорошая практика, когда 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


1

Чистый способ сделать это - ввести 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));
}

Вы можете проверить больше тестов здесь:

https://github.com/VirtualTime/VirtualTime/blob/master/VirtualTimeLib.Tests/when_virtual_time_is_used.cs

То, что дает вам расширение DateTime.Now.ToVirtualTime, является экземпляром ITime, который вы передаете методу / классу, который зависит от ITime. Некоторые DateTime.Now.ToVirtualTime настраиваются в контейнере DI на ваш выбор

Вот еще один пример внедрения в классовое устройство

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);
}

Здесь также есть хорошее прочтение о тестировании кода, которое зависит от DateTime от Ayende, здесь ayende.com/blog/3408/dealing-with-time-in-tests
Самуил

1

Мы использовали статический объект SystemTime, но столкнулись с проблемами при выполнении параллельных модульных тестов. Я попытался использовать решение Хенка Ван Бойджена, но у меня возникли проблемы в порожденных асинхронных потоках, и в итоге я использовал 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


1

Старый вопрос, но все еще в силе.

Мой подход заключается в создании нового интерфейса и класса для переноса 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.Guid.NewGuid()т. Д.

См. Https://loadlimited.visualstudio.com/Stamina/_git/Stamina.Core для получения дополнительных примеров или получения пакета nuget https://www.nuget.org/packages/Stamina.Core .


1

Вы можете изменить класс, который вы тестируете, на использование класса, Func<DateTime>который будет передаваться через его параметры конструктора, поэтому, когда вы создаете экземпляр класса в реальном коде, вы можете передать () => DateTime.UtcNowего Func<DateTime>параметру, а в тесте вы можете пройти время хочу проверить.

Например:

    [TestMethod]
    public void MyTestMethod()
    {
        var instance = new MyClass(() => DateTime.MinValue);
        Assert.AreEqual(instance.MyMethod(), DateTime.MinValue);
    } 

    public void RealWorldInitialization()
    {
        new MyClass(() => DateTime.UtcNow);
    }

    class MyClass
    {
        private readonly Func<DateTime> _utcTimeNow;

        public MyClass(Func<DateTime> UtcTimeNow)
        {
            _utcTimeNow = UtcTimeNow;
        }

        public DateTime MyMethod()
        {
            return _utcTimeNow();
        }
    }

1
Мне нравится этот ответ. Из того, что я понял, это может быть поднятие бровей в мире C #, где больше внимания уделяется классам / объектам. В любом случае, я предпочитаю это, потому что это действительно просто и ясно для меня, что происходит.
псевдорабль

0

У меня та же проблема, но я думал, что мы не должны использовать установленные даты и времени в одном классе. потому что это может привести к неправильному использованию в один прекрасный день. поэтому я использовал провайдера, как

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


0

Вот мой ответ на этот вопрос. Я комбинирую шаблон «Ambient Context» с 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 внутри юнит-теста

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

0

Используя его, 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);
}

0

Может быть, менее профессиональный, но более простое решение может быть сделать параметр DateTime в потребительском методе. Например, вместо метода make, такого как 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)
        {

        }

    }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.