Unit Testing: DateTime.Now

164

Ich habe einige Komponententests, bei denen erwartet wird, dass sich die 'aktuelle Zeit' von DateTime unterscheidet. Jetzt möchte ich die Zeit des Computers natürlich nicht ändern.

Was ist die beste Strategie, um dies zu erreichen?

Pedro
quelle
Speichern Sie einen alten Wert in der aktuellen Zeit und vergleichen Sie ihn mit datetime.now, da beim Unit-Test gefälschte Daten verwendet werden, können Sie dies problemlos tun, nicht wahr?
Prashant Lakhlani
3
Die Abstraktion der aktuellen DateTime ist nicht nur zum Testen und Debuggen, sondern auch im Produktionscode nützlich - dies hängt von den Anwendungsanforderungen ab.
Pavel Hodek
1
Verwenden Sie keine statische Klasse, um die DateTime
Daniel Little
und ein weiterer einfacher Ansatz mit VirtualTime unten
Samuel
Eine Option, die möglicherweise funktioniert und Sie können eine Überladung hinzufügen, besteht darin, eine Überladung der Methode zu erstellen, die die akzeptiert DateTime. Dies wäre diejenige, die Sie testen, die vorhandene Methode würde einfach die Überladung mit aufrufen now.
Phil Cooper

Antworten:

218

Die beste Strategie besteht darin, die aktuelle Zeit in eine Abstraktion zu verpacken und diese Abstraktion dem Verbraucher zuzuführen .


Alternativ können Sie eine Zeitabstraktion auch als Umgebungskontext definieren :

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

Auf diese Weise können Sie es wie folgt konsumieren:

var now = TimeProvider.Current.UtcNow;

In einem Komponententest können Sie durch TimeProvider.Currentein Test Double / Mock-Objekt ersetzen . Beispiel mit Moq:

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

Denken Sie jedoch beim Testen von Einheiten mit statischem Zustand immer daran, Ihr Gerät durch einen Anruf abzureißenTimeProvider.ResetToDefault() .

Mark Seemann
quelle
7
Wie können Sie im Falle eines Umgebungskontexts, selbst wenn Sie abreißen, Ihre Tests parallel ausführen?
Ilya Chernomordik
2
@IlyaChernomordik Sie sollten ILogger oder andere Querschnittsthemen nicht injizieren müssen , daher ist die Injektion eines Zeitanbieters immer noch die beste Option.
Mark Seemann
5
@MikeK Wie der erste Satz in meiner Antwort sagt: Die beste Strategie besteht darin, die aktuelle Zeit in eine Abstraktion zu verpacken und diese Abstraktion dem Verbraucher zu injizieren. Alles andere in dieser Antwort ist eine zweitbeste Alternative.
Mark Seemann
3
Hallo. Ich Noob. Wie verhindert man, dass Personen mit dieser Lösung auf DateTime.UtcNow zugreifen? Es wäre leicht für jemanden, den TimeProvider zu verpassen. Dies führt zu Fehlern beim Testen?
Obbles
1
@Obbles Code-Überprüfungen (oder Paarprogrammierung, bei der es sich um eine Form der Live-Code-Überprüfung handelt). Ich nehme an, man könnte ein Tool schreiben, das die Codebasis nach DateTime.UtcNowähnlichen durchsucht , aber Codeüberprüfungen sind trotzdem eine gute Idee.
Mark Seemann
58

Das sind alles gute Antworten, das habe ich bei einem anderen Projekt gemacht:

Verwendung:

Holen Sie sich das heutige REAL Date Time

var today = SystemTime.Now().Date;

Anstatt DateTime.Now zu verwenden, müssen SystemTime.Now()Sie ... Es ist keine schwierige Änderung, aber diese Lösung ist möglicherweise nicht für alle Projekte ideal.

Zeitreisen (Lass uns 5 Jahre in der Zukunft gehen)

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

Holen Sie sich unsere Fälschung "heute" (wird 5 Jahre von "heute" sein)

var fakeToday = SystemTime.Now().Date;

Setzen Sie das Datum zurück

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
quelle
1
Danke, ich mag diesen funktionalen Ansatz. Heute (zwei Jahre später) verwende ich immer noch eine modifizierte Version der TimeProvider-Klasse (überprüfen Sie die akzeptierte Antwort), es funktioniert wirklich gut.
Pedro
7
@crabCRUSHERclamCOLI: Wenn Sie die Idee von ayende.com/blog/3408/dealing-with-time-in-tests haben , ist es eine gute Sache, darauf zu verlinken.
Johann Gerell
3
Dieses Konzept eignet sich auch hervorragend zum Verspotten der Guid-Generierung (public static Func <Guid> NewGuid = () => Guid.NewGuid ();
mdwhatcott
2
Sie können diese nützliche Methode auch hinzufügen: public static void ResetDateTime (DateTime dateTimeNow) {var timespanDiff = TimeSpan.FromTicks (DateTime.Now.Ticks - dateTimeNow.Ticks); Now = () => DateTime.Now - timespanDiff; }
Pavel Hodek
17
Dies kann sich auf andere parallel laufende Komponententests auswirken.
Amete Blessed
24

Maulwürfe:

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

Haftungsausschluss - Ich arbeite an Maulwürfen

Peli
quelle
1
funktioniert leider nicht mit nunit und resharper.
odyth
Es sieht so aus, als ob Moles jetzt nicht mehr unterstützt wird und durch Fakes msdn.microsoft.com/en-us/library/… ersetzt wird. Zweifellos wird MS dies zu früh
einstellen
17

Sie haben einige Möglichkeiten, dies zu tun:

  1. Verwenden Sie das Mocking-Framework und einen DateTimeService (Implementieren Sie eine kleine Wrapper-Klasse und fügen Sie sie in den Produktionscode ein). Die Wrapper-Implementierung greift auf DateTime zu und in den Tests können Sie die Wrapper-Klasse verspotten.

  2. Verwenden Sie Typemock Isolator , es kann DateTime.Now fälschen und erfordert nicht, dass Sie den zu testenden Code ändern.

  3. Verwenden Sie Maulwürfe , es kann auch DateTime.Now fälschen und erfordert keine Änderung des Produktionscodes.

Einige Beispiele:

Wrapper-Klasse mit 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; } }
}

Fälschen von DateTime direkt mit Isolator:

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

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

Haftungsausschluss - Ich arbeite bei Typemock

Elisha
quelle
12

Fügen Sie eine gefälschte Baugruppe für das System hinzu (klicken Sie mit der rechten Maustaste auf Systemreferenz => Gefälschte Baugruppe hinzufügen).

Und schreiben Sie in Ihre Testmethode:

using (ShimsContext.Create())
{
   System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10);
   MethodThatUsesDateTimeNow();
}
Dave
quelle
Wenn Sie Zugriff auf Vs Premium oder Ultimate haben, ist dies bei weitem der einfachste / schnellste Weg, dies zu tun.
Rugdr
7

In Bezug auf die Antwort von @crabcrusherclamcollector gibt es ein Problem bei der Verwendung dieses Ansatzes in EF-Abfragen (System.NotSupportedException: Der LINQ-Ausdrucksknotentyp 'Invoke' wird in LINQ to Entities nicht unterstützt). Ich habe die Implementierung dahingehend geändert:

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
quelle
Ich vermute mit EF meinst du Entity Framework? Wie sieht das Objekt aus, für das Sie SystemTime verwenden? Ich vermute, Sie haben einen Verweis auf SystemTime für die tatsächliche Entität. Stattdessen sollten Sie ein reguläres DateTime-Objekt für Ihre Entität verwenden und es mit SystemTime festlegen, wo immer es in Ihrer Anwendung
sinnvoll ist
1
Ja, ich habe es getestet, als ich eine Linq-Abfrage und EntityFramework6 erstellt und in dieser Abfrage UtcNow von SystemTime aus referenziert habe. Es gab eine Ausnahme wie beschrieben. Nach dem Ändern der Implementierung funktioniert es gut.
Marcinn
Ich liebe diese Lösung! Toll!
mirind4
7

Um einen Code zu testen, der von der abhängt System.DateTime, system.dllmuss der verspottet werden.

Es gibt zwei Rahmen, von denen ich weiß, dass sie dies tun. Microsoft Fälschungen und Kittel .

Microsoft-Fälschungen erfordern das Ultimatum von Visual Studio 2012 und funktionieren direkt aus dem Compton heraus.

Smocks ist Open Source und sehr einfach zu bedienen. Es kann mit NuGet heruntergeladen werden.

Das Folgende zeigt ein Modell von System.DateTime:

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

   // Outputs "2000"
   Console.WriteLine(DateTime.Now.Year);
});
persianLife
quelle
5

Ein Thread sicher SystemClockmit ThreadLocal<T>funktioniert funktioniert gut für mich.

ThreadLocal<T> ist in .Net Framework v4.0 und höher verfügbar.

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

Anwendungsbeispiel:

[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
quelle
1
+! Thread local sollte Probleme mit der parallelen Testausführung und mehreren Tests vermeiden, die möglicherweise die aktuelle NowInstanz überschreiben .
Hux
1
Versucht, dies zu verwenden, hatte aber ein Problem über Threads hinweg, also haben wir ein ähnliches Prinzip gewählt, aber AsyncLocalnichtThreadLocal
rrrr
@rrrr: Kannst du das Problem erklären, das du hattest?
Henk van Boeijen
@HenkvanBoeijen Sicher, wir hatten Probleme, als die Zeit eingestellt wurde, und warteten dann auf eine asynchrone Methode. 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 Ich habe eine Antwort mit dem hinzugefügt, was ich am Ende mit Link
rrrr
3

Ich bin auf dasselbe Problem gestoßen, habe aber ein Forschungsprojekt von Microsoft gefunden, das dieses Problem löst.

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

Moles ist ein leichtes Framework für Teststubs und Umwege in .NET, das auf Delegaten basiert. Maulwürfe können verwendet werden, um jede .NET-Methode zu umgehen, einschließlich nicht virtueller / statischer Methoden in versiegelten Typen

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

Der Beispielcode wurde gegenüber dem Original geändert.

Ich hatte getan, was andere vorgeschlagen hatten, und die DateTime in einen Anbieter abstrahiert. Es fühlte sich einfach falsch an und ich hatte das Gefühl, dass es zu viel war, nur um es zu testen. Ich werde dies heute Abend in mein persönliches Projekt umsetzen.

Bobby Cannon
quelle
2
Übrigens funktioniert dies nur mit VS2010. Ich war verärgert, als ich herausfand, dass Moles jetzt Fakes für VS2012 ist und nur für Premium und Ultimate Visual Studios verfügbar ist. Keine Fälschungen für VS 2012 Professional. Also habe ich das nie benutzt. :(
Bobby Cannon
3

Ein besonderer Hinweis zum Verspotten DateTime.Nowmit TypeMock ...

Der Wert von DateTime.Nowmuss in eine Variable eingefügt werden, damit dies richtig verspottet wird. Beispielsweise:

Das funktioniert nicht:

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

Dies bedeutet jedoch:

var currentDateTime = DateTime.Now;
if ((currentDateTime - message.TimeOpened.Value) > new TimeSpan(1, 0, 0))
Dave Black
quelle
2

Scheinobjekte.

Eine nachgebildete DateTime, die ein Jetzt zurückgibt, das für Ihren Test geeignet ist.

S.Lott
quelle
2

Ich bin überrascht, dass niemand einen der naheliegendsten Wege vorgeschlagen hat:

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

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

Dann können Sie diese Methode in Ihrem Testdouble einfach überschreiben.

TimeProviderIn einigen Fällen mag ich es auch, eine Klasse zu injizieren , aber für andere ist dies mehr als genug. Ich würde die TimeProviderVersion wahrscheinlich bevorzugen, wenn Sie dies in mehreren Klassen wiederverwenden müssen.

BEARBEITEN: Für alle Interessierten wird dies als Hinzufügen einer "Naht" zu Ihrer Klasse bezeichnet. Dies ist ein Punkt, an dem Sie sich an das Verhalten der Klasse anschließen können, um sie (zu Testzwecken oder auf andere Weise) zu ändern, ohne den Code in der Klasse tatsächlich ändern zu müssen.

Sara
quelle
1

Eine gute Vorgehensweise ist, wenn DateTimeProvider IDisposable implementiert.

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

Als nächstes können Sie Ihre gefälschte DateTime für Unit-Tests injizieren

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

Siehe Beispiel Beispiel für DateTimeProvider

Mino
quelle
1

Eine saubere Möglichkeit, dies zu tun, besteht darin, VirtualTime zu injizieren. Damit können Sie die Zeit steuern. Installieren Sie zuerst VirtualTime

Install-Package VirtualTime

So können Sie beispielsweise die Zeit bei allen Aufrufen von DateTime.Now oder UtcNow um das Fünffache verkürzen

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

Um die Zeit langsamer zu machen, z. B. fünfmal langsamer

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

Um die Zeit zum Stillstand zu bringen, tun Sie dies

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

Eine zeitliche Rückwärtsbewegung ist noch nicht getestet

Hier ist ein Beispieltest:

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

Weitere Tests finden Sie hier:

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

Die DateTime.Now.ToVirtualTime-Erweiterung bietet Ihnen eine Instanz von ITime, die Sie an eine Methode / Klasse übergeben, die von ITime abhängt. Einige DateTime.Now.ToVirtualTime werden im DI-Container Ihrer Wahl eingerichtet

Hier ist ein weiteres Beispiel für das Einspritzen in einen Klassenkonfustor

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
quelle
Es gibt auch eine gute Lektüre über das Testen von Code, der von DateTime von Ayende abhängt, hier ayende.com/blog/3408/dealing-with-time-in-tests
Samuel
1

Wir haben ein statisches SystemTime-Objekt verwendet, sind jedoch auf Probleme beim Ausführen paralleler Komponententests gestoßen. Ich habe versucht, die Lösung von Henk van Boeijen zu verwenden, hatte jedoch Probleme mit erzeugten asynchronen Threads und habe AsyncLocal auf ähnliche Weise wie unten verwendet:

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

Quelle: https://gist.github.com/CraftyFella/42f459f7687b0b8b268fc311e6b4af08

rrrr
quelle
1

Eine alte Frage, aber immer noch gültig.

Mein Ansatz ist es, eine neue Schnittstelle und Klasse zu erstellen, um den System.DateTime.NowAnruf zu beenden

public interface INow
{
    DateTime Execute();
}

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

Diese Schnittstelle kann in jede Klasse eingefügt werden, die das aktuelle Datum und die aktuelle Uhrzeit abrufen muss. In diesem Beispiel habe ich eine Klasse, die dem aktuellen Datum und der aktuellen Uhrzeit eine Zeitspanne hinzufügt (eine Einheit, die auf System.DateTime.Now.Add (TimeSpan) getestet werden kann).

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

Und Tests können geschrieben werden, um sicherzustellen, dass es richtig funktioniert. Ich verwende NUnit und Moq, aber jedes Testframework reicht aus

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

Dieses Muster wird für alle Funktionen arbeitet , die von externen Faktoren abhängen, wie System.Random.Next(), System.DateTime.Now.UtcNow, System.Guid.NewGuid()usw.

Siehe https://loadlimited.visualstudio.com/Stamina/_git/Stamina.Core für weitere Beispiele oder erhalten die https://www.nuget.org/packages/Stamina.Core nuget Paket.

Kevin Brydon
quelle
1

Sie können die Klasse, die Sie testen, so ändern, dass eine Klasse verwendet Func<DateTime>wird, die über die Konstruktorparameter übergeben wird. Wenn Sie also eine Instanz der Klasse in echtem Code erstellen, können Sie sie () => DateTime.UtcNowan den Func<DateTime>Parameter übergeben und beim Test die Zeit, die Sie benötigen möchte testen.

Beispielsweise:

    [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
quelle
1
Ich mag diese Antwort. Nach allem, was ich zusammengetragen habe, könnte dies in der C # -Welt, in der der Schwerpunkt mehr auf Klassen / Objekten liegt, Augenbrauen hochziehen. In jedem Fall bevorzuge ich dies, weil es mir wirklich einfach und klar ist, was passiert.
Pseudoramble
0

Ich hatte das gleiche Problem, aber ich dachte, wir sollten die festgelegten datetime-Dinge nicht für dieselbe Klasse verwenden. weil es eines Tages zu Missbrauch führen könnte. also habe ich den anbieter gerne benutzt

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

Für Tests machte das Testprojekt einen Helfer, der sich mit festgelegten Dingen befasst,

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

auf Code

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

und auf Tests

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

Aber eines muss man sich merken: Manchmal verhalten sich die echte DateTime und die DateTime des Anbieters nicht gleich

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

Ich nahm an, dass die Ehrerbietung maximal TimeSpan.FromMilliseconds (0.00002) sein würde . Aber meistens ist es noch weniger

Finden Sie die Probe bei MockSamples

Dipon Roy
quelle
0

Hier ist meine Antwort auf diese Frage. Ich kombiniere das 'Ambient Context'-Muster mit IDisposable. Sie können also DateTimeProvider.Current in Ihrem normalen Programmcode verwenden und im Test den Bereich mit einer using-Anweisung überschreiben.

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

Hier erfahren Sie, wie Sie den obigen DateTimeProvider in einem Unit-Test verwenden

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
quelle
0

Mit waren ITimeProviderwir gezwungen, es in ein spezielles gemeinsames Projekt aufzunehmen, auf das von den anderen Projekten verwiesen werden muss. Dies erschwerte jedoch die Kontrolle von Abhängigkeiten .

Wir haben ITimeProviderim .NET Framework nach dem gesucht . Wir haben nach dem NuGet-Paket gesucht und eines gefunden , mit dem wir nicht arbeiten können DateTimeOffset.

Deshalb haben wir eine eigene Lösung entwickelt, die nur von den Typen der Standardbibliothek abhängt. Wir verwenden eine Instanz von Func<DateTimeOffset>.

Wie benutzt man

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

Wie man sich registriert

Autofac

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

( Für zukünftige Redakteure: Fügen Sie Ihre Fälle hier hinzu ).

So testen Sie Einheiten

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
quelle
0

Möglicherweise könnte eine weniger professionelle, aber einfachere Lösung darin bestehen, einen DateTime-Parameter bei der Consumer-Methode zu erstellen. Beispielsweise können Sie anstelle von make-Methoden wie SampleMethod SampleMethod1 mit parameter erstellen. Das Testen von SampleMethod1 ist einfacher

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
quelle