Testowanie jednostkowe: DateTime.Now

164

Mam kilka testów jednostkowych, w których oczekuje się, że `` bieżący czas '' będzie inny niż DateTime.Now i oczywiście nie chcę zmieniać czasu komputera.

Jaka jest najlepsza strategia, aby to osiągnąć?

Pedro
źródło
zapisz jakąś starą wartość w bieżącym czasie i porównaj ją z datetime.now, ponieważ testy jednostkowe wykorzystują fałszywe dane, możesz to łatwo zrobić, prawda?
Prashant Lakhlani
3
Zapewnienie abstrakcji aktualnego DateTime jest przydatne nie tylko do testowania i debugowania, ale także w kodzie produkcyjnym - zależy to od potrzeb aplikacji.
Pavel Hodek,
1
Nie używaj klasy statycznej, aby uzyskać DateTime
Daniel Little
i inne proste podejście z wykorzystaniem VirtualTime poniżej
Samuel
Jedną z opcji, która może działać i możesz dodać przeciążenie, jest utworzenie przeciążenia metody, która akceptuje DateTime. To będzie ten, który testujesz, istniejąca metoda po prostu wywoła przeciążenie z now.
Phil Cooper

Odpowiedzi:

219

Najlepszą strategią jest owinąć aktualny czas w abstrakcji i wstrzyknąć tę abstrakcję do konsumenta .


Alternatywnie możesz również zdefiniować abstrakcję czasu jako kontekst otoczenia :

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

Umożliwi to spożywanie go w następujący sposób:

var now = TimeProvider.Current.UtcNow;

W teście jednostkowym można zamienić TimeProvider.Currentna obiekt Test Double / Mock. Przykład przy użyciu Moq:

var timeMock = new Mock<TimeProvider>();
timeMock.SetupGet(tp => tp.UtcNow).Returns(new DateTime(2010, 3, 11));
TimeProvider.Current = timeMock.Object;

Jednak podczas testów jednostkowych ze stanem statycznym zawsze pamiętaj, aby zburzyć urządzenie , wywołując TimeProvider.ResetToDefault().

Mark Seemann
źródło
7
W przypadku kontekstu otoczenia, nawet jeśli zrywasz, jak sprawić, by testy były wykonywane równolegle?
Ilya Chernomordik
2
@IlyaChernomordik Nie powinieneś być zmuszony do wstrzykiwania ILoggera lub innych problemów związanych z przekrojami , więc wstrzyknięcie dostawcy czasu jest nadal najlepszą opcją.
Mark Seemann
5
@MikeK Jak w pierwszym zdaniu mojej odpowiedzi brzmi: najlepszą strategią jest owinięcie aktualnego czasu abstrakcją i wstrzyknięcie tej abstrakcji konsumentowi. Wszystko inne w tej odpowiedzi jest drugą najlepszą alternatywą.
Mark Seemann
3
Cześć. I Noob. Jak można uniemożliwić ludziom dostęp do DateTime.UtcNow za pomocą tego rozwiązania? Ktoś mógłby łatwo przegapić używanie TimeProvider. Prowadzi to do błędów w testowaniu?
Obbles
1
Recenzje @Bbles Code (lub programowanie w parach, które jest formą przeglądu kodu na żywo). Przypuszczam, że można by napisać narzędzie, które skanuje bazę kodu DateTime.UtcNowi podobne, ale przeglądy kodu i tak są dobrym pomysłem.
Mark Seemann
58

To wszystko są dobre odpowiedzi, oto co zrobiłem przy innym projekcie:

Stosowanie:

Uzyskaj dzisiejszą PRAWDZIWĄ datę i godzinę

var today = SystemTime.Now().Date;

Zamiast korzystać z DateTime.Now, musisz użyć SystemTime.Now()... To nie jest trudna zmiana, ale to rozwiązanie może nie być idealne dla wszystkich projektów.

Podróże w czasie (przejdźmy 5 lat w przyszłość)

SystemTime.SetDateTime(today.AddYears(5));

Zdobądź naszą podróbkę „dzisiaj” (będzie za 5 lat od „dzisiaj”)

var fakeToday = SystemTime.Now().Date;

Wyzeruj datę

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;
    }
}
crabCRUSHERclamCOLLECTOR
źródło
1
Dziękuję, podoba mi się to funkcjonalne podejście. Dziś (dwa lata później) nadal korzystam ze zmodyfikowanej wersji klasy TimeProvider (sprawdź zaakceptowaną odpowiedź), działa naprawdę fajnie.
Pedro
7
@crabCRUSHERclamCOLI: Jeśli masz pomysł z ayende.com/blog/3408/dealing-with-time-in-tests , to dobrze jest umieścić do niego link.
Johann Gerell
3
Ta koncepcja działa również świetnie do kpiny z generowania Guid (public static Func <Guid> NewGuid = () => Guid.NewGuid ();
mdwhatcott,
2
Możesz również dodać tę przydatną metodę: public static void ResetDateTime (DateTime dateTimeNow) {var timespanDiff = TimeSpan.FromTicks (DateTime.Now.Ticks - dateTimeNow.Ticks); Now = () => DateTime.Now - timespanDiff; }
Pavel Hodek,
17
bardzo niebezpieczne. Może to wpłynąć na inne testy jednostkowe działające równolegle.
Amete Błogosławiony
24

Mole:

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

Zastrzeżenie - pracuję nad Molesami

Peli
źródło
1
niestety nie działa z zakonnicą i reszkarzem.
odyth
Wygląda na to, że Moles jest teraz nieobsługiwany, zastąpiony przez Fakes msdn.microsoft.com/en-us/library/… , bez wątpienia MS przerwie to zbyt wcześnie
danio
17

Masz kilka opcji, aby to zrobić:

  1. Użyj mocking framework i użyj DateTimeService (zaimplementuj małą klasę opakowania i wstrzyknij ją do kodu produkcyjnego). Implementacja opakowania będzie miała dostęp do DateTime iw testach będzie można mockować klasę opakowania.

  2. Użyj izolatora Typemock , może on sfałszować DateTime.Now i nie będzie wymagał zmiany testowanego kodu.

  3. Używaj Moli , może również fałszować DateTime.Now i nie będzie wymagać zmian w kodzie produkcyjnym.

Kilka przykładów:

Klasa opakowania używająca 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 bezpośrednio za pomocą Isolatora:

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

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

Zastrzeżenie - pracuję w Typemock

Elizeusz
źródło
12

Dodaj fałszywy zespół dla systemu (kliknij prawym przyciskiem myszy odniesienie do systemu => Dodaj fałszywy zespół).

I napisz do swojej metody testowej:

using (ShimsContext.Create())
{
   System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10);
   MethodThatUsesDateTimeNow();
}
dave
źródło
jeśli masz dostęp do Vs Premium lub Ultimate Jest to zdecydowanie najłatwiejszy / najszybszy sposób na zrobienie tego.
Rugdr
7

W odniesieniu do odpowiedzi @crabcrusherclamcollector występuje problem podczas korzystania z tego podejścia w zapytaniach EF (System.NotSupportedException: typ węzła wyrażenia LINQ „Invoke” nie jest obsługiwany w LINQ to Entities). Zmodyfikowałem implementację na:

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;
            }
        }
    }
marcinn
źródło
Zgaduję, przez EF masz na myśli Entity Framework? Jak wygląda obiekt, na którym używasz SystemTime? Domyślam się, że masz odniesienie do SystemTime w rzeczywistej encji, zamiast tego powinieneś użyć zwykłego obiektu DateTime na swojej encji i ustawić go za pomocą SystemTime wszędzie tam, gdzie ma to sens w twojej aplikacji
crabCRUSHERclamCOLLECTOR
1
Tak, przetestowałem to podczas tworzenia zapytania linq i EntityFramework6 i odwoływania się w tym zapytaniu do UtcNow z SystemTime. Wystąpił wyjątek, jak opisano. Po zmianie implementacji działa dobrze.
marcinn
Uwielbiam to rozwiązanie! Wspaniały!
mirind4
7

Aby przetestować kod, który zależy od System.DateTime, system.dllnależy wyszydzić.

Są dwa znane mi ramy, które to robią. Microsoft podróbki i fartuchy .

Podróbki Microsoftu wymagają ultimatum Visual Studio 2012 i działają prosto z compton.

Smocks to open source i bardzo łatwy w użyciu. Można go pobrać za pomocą NuGet.

Poniżej przedstawiono przykład System.DateTime:

Smock.Run(context =>
{
  context.Setup(() => DateTime.Now).Returns(new DateTime(2000, 1, 1));

   // Outputs "2000"
   Console.WriteLine(DateTime.Now.Year);
});
persianLife
źródło
5

Bezpieczne wątek SystemClockużywając ThreadLocal<T>działa świetnie dla mnie.

ThreadLocal<T> jest dostępny w .Net Framework v4.0 i nowszych.

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

Przykład użycia:

[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();
}
Henk van Boeijen
źródło
1
+! Wątek lokalny powinien unikać problemów z równoległym wykonywaniem testów i wieloma testami, które mogą zastąpić bieżące Nowwystąpienie.
Hux
1
Starał się to wykorzystać, ale miał problem w całej wątków tak przeszli z podobnym zleceniodawcy, ale stosując AsyncLocalzamiastThreadLocal
RRRR
@rrrr: czy możesz wyjaśnić swój problem?
Henk van Boeijen
@HenkvanBoeijen Jasne, napotkaliśmy problemy podczas ustawiania czasu, a potem oczekiwaliśmy na metodę asynchroniczną. 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 Dodałem odpowiedź z tym, co skończyło się za pomocą linku
rrrr
3

Napotkałem ten sam problem, ale znalazłem projekt badawczy firmy Microsoft, który rozwiązuje ten problem.

http://research.microsoft.com/en-us/projects/moles/

Moles to lekka struktura dla testowych kodów pośredniczących i objazdów w .NET, która jest oparta na delegatach. Moli można używać do obejścia dowolnej metody .NET, w tym metod niewirtualnych / statycznych w typach zamkniętych

// 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!");
}

Przykładowy kod został zmodyfikowany w stosunku do oryginału.

Zrobiłem to, co sugerowali inni, i wyodrębniłem DateTime do dostawcy. Czułem się po prostu źle i czułem, że to za dużo na samo testowanie. Dziś wieczorem wprowadzę to do mojego osobistego projektu.

Bobby Cannon
źródło
2
BTW, działa to tylko z VS2010. Byłem zdenerwowany, gdy odkryłem, że Moles jest teraz Fakes dla VS2012 i jest dostępny tylko dla Premium i Ultimate Visual Studios. Bez podróbek dla VS 2012 Professional. Więc nigdy nie udało mi się tego użyć. :(
Bobby Cannon,
3

Jedna uwaga specjalna na temat kpiny DateTime.Nowz TypeMock ...

Wartość DateTime.Now musi być umieszczona w zmiennej, aby została poprawnie wyśmiana. Na przykład:

To nie działa:

if ((DateTime.Now - message.TimeOpened.Value) > new TimeSpan(1, 0, 0))

Jednak to robi:

var currentDateTime = DateTime.Now;
if ((currentDateTime - message.TimeOpened.Value) > new TimeSpan(1, 0, 0))
Dave Black
źródło
2

Mock Objects.

Mock DateTime, który zwraca wartość Now, która jest odpowiednia dla Twojego testu.

S.Lott
źródło
2

Dziwię się, że nikt nie zasugerował jednej z najbardziej oczywistych dróg:

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

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

Następnie możesz po prostu zastąpić tę metodę w swoim podwójnym teście.

TimeProviderW niektórych przypadkach lubię też wstrzykiwać klasę, ale w innych jest to więcej niż wystarczające. Prawdopodobnie wolałbym tę TimeProviderwersję, gdybyś chciał użyć jej ponownie w kilku klasach.

EDYCJA: Dla każdego, kto jest zainteresowany, nazywa się to dodaniem „szwu” do klasy, czyli punktem, w którym można podłączyć się do jego zachowania, aby je zmodyfikować (do celów testowych lub w inny sposób) bez faktycznej zmiany kodu w klasie.

sara
źródło
1

Dobra praktyka jest taka, gdy DateTimeProvider implementuje 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; 
    } 
} 

Następnie możesz wstrzyknąć fałszywą datę i godzinę do testów jednostkowych

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

Zobacz przykład Przykład DateTimeProvider

Mino
źródło
1

Prostym sposobem na to jest wstrzyknięcie VirtualTime. Pozwala kontrolować czas. Najpierw zainstaluj VirtualTime

Install-Package VirtualTime

Pozwala to na przykład na 5-krotne przyspieszenie czasu przy wszystkich wywołaniach funkcji DateTime.Now lub UtcNow

var DateTime = DateTime.Now.ToVirtualTime(5);

Aby czas płynął wolniej, np. 5 razy wolniej

var DateTime = DateTime.Now.ToVirtualTime(0.5);

Aby czas się zatrzymał, rób to

var DateTime = DateTime.Now.ToVirtualTime(0);

Cofanie się w czasie nie jest jeszcze testowane

Oto przykładowy test:

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

Możesz sprawdzić więcej testów tutaj:

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

To, co daje rozszerzenie DateTime.Now.ToVirtualTime, to instancja ITime, którą przekazujesz do metody / klasy zależnej od ITime. Niektóre DateTime.Now.ToVirtualTime są konfigurowane w wybranym kontenerze DI

Oto kolejny przykład wstrzyknięcia do contrustora klasy

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);
}
Samuel
źródło
Jest też dobra lektura na temat testowania kodu zależnego od DateTime autorstwa Ayende tutaj ayende.com/blog/3408/dealing-with-time-in-tests
Samuel
1

Używaliśmy statycznego obiektu SystemTime, ale napotkaliśmy problemy podczas wykonywania równoległych testów jednostkowych. Próbowałem użyć rozwiązania Henka van Boeijena, ale miałem problemy z odrodzonymi wątkami asynchronicznymi, skończyło się na użyciu AsyncLocal w sposób podobny do tego poniżej:

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

Źródło: https://gist.github.com/CraftyFella/42f459f7687b0b8b268fc311e6b4af08

rrrr
źródło
1

Stare pytanie, ale wciąż aktualne.

Moje podejście polega na utworzeniu nowego interfejsu i klasy do zawijania System.DateTime.Nowpołączenia

public interface INow
{
    DateTime Execute();
}

public sealed class Now : INow
{
    public DateTime Execute()
    {
        return DateTime.Now
    }
}

Ten interfejs można wstrzyknąć do dowolnej klasy, która musi pobierać aktualną datę i godzinę. W tym przykładzie mam klasę, która dodaje przedział czasu do bieżącej daty i godziny (jednostka testowalna 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);
    }
}

Można też napisać testy, aby upewnić się, że działa poprawnie. Używam NUnit i Moq, ale wystarczy każda platforma testowa

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

Ten wzór będzie pracować dla wszelkich funkcji, które są zależne od czynników zewnętrznych, takich jak System.Random.Next(), System.DateTime.Now.UtcNow, System.Guid.NewGuid()itp

Zobacz https://loadlimited.visualstudio.com/Stamina/_git/Stamina.Core, aby uzyskać dalsze przykłady lub pobierz pakiet nuget https://www.nuget.org/packages/Stamina.Core .

Kevin Brydon
źródło
1

Możesz zmienić klasę, którą testujesz, aby używała klasy, Func<DateTime>która zostanie przekazana przez jej parametry konstruktora, więc kiedy tworzysz instancję klasy w prawdziwym kodzie, możesz przekazać () => DateTime.UtcNowdoFunc<DateTime> parametru, a podczas testu możesz spędzić czas chcesz przetestować.

Na przykład:

    [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();
        }
    }
Nir
źródło
1
Podoba mi się ta odpowiedź. Z tego, co zebrałem, może to oznaczać podniesienie brwi w świecie C #, w którym bardziej koncentruje się na klasach / obiektach. Tak czy inaczej, wolę to, ponieważ jest dla mnie naprawdę proste i jasne, co się dzieje.
pseudoramble
0

Mam ten sam problem, ale myślę, że nie powinniśmy używać rzeczy z ustawioną datą i godziną na tej samej klasie. ponieważ pewnego dnia może to doprowadzić do niewłaściwego użycia. więc korzystałem z dostawcy

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

Do testów, w projekcie testowym stworzyłem pomocnika, który zajmie się zadanymi rzeczami,

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

na kodzie

var dateTimeNow = DateTimeProvider.DateTime.Now         //not DateTime.Now
var dateTimeUtcNow = DateTimeProvider.DateTime.UtcNow   //not DateTime.UtcNow

i na testach

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

Ale trzeba pamiętać o jednej rzeczy, czasami prawdziwy DateTime i DateTime dostawcy nie działają tak samo

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

Przyjąłem, że szacunek będzie maksymalny TimeSpan.FromMilliseconds (0,00002) . Ale w większości przypadków jest to jeszcze mniej

Znajdź próbkę w MockSamples

Dipon Roy
źródło
0

Oto moja odpowiedź na to pytanie. Łączę wzorzec „Ambient Context” z IDisposable. Możesz więc użyć DateTimeProvider.Current w swoim normalnym kodzie programu, aw teście przesłonisz zakres za pomocą instrukcji 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; 
    }
}
}

Oto jak używać powyższego DateTimeProvider w ramach testu jednostkowego

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;
        }
    }
}
Stefc
źródło
0

Korzystając z niego ITimeProvider, byliśmy zmuszeni przenieść go do specjalnego wspólnego projektu, do którego należy odwołać się z pozostałych projektów. Ale to skomplikowało kontrolę zależności .

Szukaliśmy ITimeProviderw .NET Framework. Szukaliśmy pakietu NuGet i znaleźliśmy taki, z którym nie można współpracować DateTimeOffset.

Wymyśliliśmy więc własne rozwiązanie, które zależy tylko od typów standardowej biblioteki. Używamy wystąpienia Func<DateTimeOffset>.

Jak używać

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

Jak zarejestrować

Autofac

builder.RegisterInstance<Func<DateTimeOffset>>(() => DateTimeOffset.Now);

( Dla przyszłych redaktorów: dołącz tutaj swoje przypadki ).

Jak przeprowadzić test jednostkowy

public void MakeIllustratingTuple_WhenCalled_FillsCreatedAt()
{
    DateTimeOffset expected = CreateRandomDateTimeOffset();
    DateTimeOffset StubNow() => expected;
    var thing = new ThingThatNeedsTimeProvider(StubNow);

    var (_, actual) = thing.MakeIllustratingTuple();

    Assert.AreEqual(expected, actual);
}
Mark Shevchenko
źródło
0

Być może mniej profesjonalne, ale prostsze rozwiązanie mogłoby polegać na wprowadzeniu parametru DateTime w metodzie konsumenckiej.

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)
        {

        }

    }
Mehmet
źródło