Тестування блоку: DateTime.Now


164

У мене є кілька тестових одиниць, які очікують, що "поточний час" буде іншим, ніж DateTime.Now, і я не хочу змінювати час комп'ютера, очевидно.

Яка найкраща стратегія для досягнення цього?


збережіть деяке старе значення у поточному часі та порівняйте його з datetime.now, оскільки тестування модулів використовує підроблені дані, ви можете це легко зробити, чи не так?
Прашант Лахлані

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

1
Не використовуйте статичний клас, щоб отримати DateTime
Daniel Little

і ще один простий підхід із використанням 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. Таким чином, ведучи до помилок у тестуванні?
Обличчя

1
@ Огляди коду відгуків (або парне програмування, що є формою огляду живого коду). Я припускаю, що можна написати інструмент, який сканує базу коду для 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 (public static Func <Guid> NewGuid = () => Guid.NewGuid ();
mdwhatcott

2
Ви також можете додати цей корисний метод: загальнодоступна статична недійсність ResetDateTime (DateTime dateTimeNow) {var timespanDiff = TimeSpan.FromTicks (DateTime.Now.Ticks - dateTimeNow.Ticks); Тепер = () => 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
}

Відмова - я працюю над Moles


1
на жаль, не працює з nunit та resharper.
Одіт

Схоже, Moles зараз не підтримується, замінений на Fakes msdn.microsoft.com/en-us/library/… , без сумніву, MS відмовиться від цього занадто рано
danio

17

У вас є кілька варіантів для цього:

  1. Використовуйте глузуючий фреймворк та використовуйте DateTimeService (реалізуйте невеликий клас обгортки та вставте його у виробничий код). Реалізація обгортки отримає доступ до DateTime, і в тестах ви зможете знущатися над класом обгортки.

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

  3. Використовуйте Moles , він також може підробляти 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 безпосередньо за допомогою Isolator:

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

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

Відмова від відповідальності - я працюю в Typemock


12

Додайте підроблену збірку для System (клацніть правою кнопкою миші на System reference => Add fake Assembly).

І запишіть у свій метод тестування:

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 для сутностей). Я змінив реалізацію на це:

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. Був виняток, як описано. Після зміни реалізації він працює добре.
marcinn

Я люблю це рішення! Чудово!
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: чи можете ви пояснити проблему, яку ви мали?
Henk van Boeijen

@HenkvanBoeijen Звичайно, ми стикалися з проблемами, коли був встановлений час, і тоді нас чекав метод асинхронізації. Console.WriteLine(SystemClock.Now); SystemClock.Set(new DateTime(2017, 01, 01)); Console.WriteLine(SystemClock.Now); await Task.Delay(1000); Console.WriteLine(SystemClock.Now);
rrrr

@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 зараз підробляє для VS2012, і він доступний лише для преміум і 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, який повертає Асистент, що підходить для вашого тесту.


2

Я здивований, що ніхто не запропонував одного з найбільш очевидних способів:

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

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

Тоді ви можете просто перекрити цей метод у вашому тестовому дублі.

Мені також подобається вводити TimeProviderклас в деяких випадках, але для інших цього більш ніж достатньо. Я, мабуть, віддаю перевагу TimeProviderверсії, якщо вам потрібно повторно використовувати це в декількох класах.

EDIT: Для всіх, хто цікавиться, це називається додаванням «шва» до вашого класу, моменту, коли ви можете підключити його поведінку, щоб змінити його (з метою тестування чи іншим способом), не змінюючи код у класі.


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 для подальших прикладів або отримайте 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

Ось моя відповідь на це питання. Я поєдную схему "Ембієнтний контекст" з IDisposable. Таким чином, ви можете використовувати DateTimeProvider.Current у своєму звичайному програмному коді, а в тесті ви переосмислюєте область з використанням оператора.

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 рамках. Ми шукали пакет 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());
    }
}

Як зареєструватися

Автофактор

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 за споживчим методом. Наприклад, замість того, щоб зробити метод, подібний 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.