Birim Testi: DateTime.Now


164

Ben 'şimdiki zaman' DateTime.Now farklı olmasını beklemek bazı birim testleri var.Şimdi ve ben açıkçası, bilgisayarın saatini değiştirmek istemiyorum.

Bunu başarmak için en iyi strateji nedir?


bazı eski değerleri geçerli zamanda saklayın ve datetime.now ile karşılaştırın, Birim testi sahte verileri kullandığından bunu kolayca yapabilirsiniz, değil mi?
Prashant Lakhlani

3
Geçerli DateTime'ın soyutlanmasını sağlamak yalnızca test ve hata ayıklama için değil, aynı zamanda üretim kodunda da yararlıdır - uygulama gereksinimlerine bağlıdır.
Pavel Hodek

1
DateTime'ı almak için statik bir sınıf kullanmayın
Daniel Little

ve aşağıdaki VirtualTime kullanarak basit bir yaklaşım daha
Samuel

Çalışabilecek ve aşırı yük ekleyebileceğiniz bir seçenek, kabul eden yöntemin aşırı yüklenmesini oluşturmaktır DateTime. Bu test ettiğiniz yöntem olacaktır, mevcut yöntem sadece aşırı yüklenmeyi çağırır now.
Phil Cooper

Yanıtlar:


219

En iyi strateji, şimdiki zamanı bir soyutlamaya sarmak ve bu soyutlamayı tüketiciye enjekte etmektir .


Alternatif olarak , zaman soyutlamasını Ortam Bağlamı olarak da tanımlayabilirsiniz :

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

Bu, onu şu şekilde tüketmenizi sağlayacaktır:

var now = TimeProvider.Current.UtcNow;

Birim testinde, TimeProvider.CurrentTest Double / Mock nesnesiyle değiştirebilirsiniz. Adedi kullanan örnek:

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

Bununla birlikte, statik durumla birim testi yaparken, her zaman arayarak fikstürünüzü yıkmayı unutmayınTimeProvider.ResetToDefault() .


7
Ortam koşulları söz konusu olduğunda, yıkılsanız bile, testlerinizin paralel çalışmasını nasıl sağlarsınız?
Ilya Chernomordik

2
@IlyaChernomordik ILogger veya diğer Kesişen Endişeleri enjekte etmek zorunda kalmamalısınız , bu nedenle bir zaman sağlayıcı enjekte etmek hala en iyi seçenektir.
Mark Seemann

5
Cevabımdaki ilk cümlenin söylediği gibi: En iyi strateji, şimdiki zamanı bir soyutlamaya sarmak ve bu soyutlamayı tüketiciye enjekte etmektir. Bu cevaptaki diğer her şey ikinci en iyi alternatif.
Mark Seemann

3
Selam. Ben Noob. İnsanların DateTime.UtcNow'a bu çözümle erişmesini nasıl önler? Birinin TimeProvider'ı kullanarak kaçırması kolay olurdu. Böylece test hatalara yol açar?
Obbles

1
@Obbles Kod incelemeleri (veya canlı kod incelemesinin bir biçimi olan çift programlama). Biri için kod tabanı tarar DateTime.UtcNowve benzer bir araç yazabilirim , ama kod değerlendirmeleri zaten iyi bir fikir.
Mark Seemann

58

Bunların hepsi iyi cevaplar, farklı bir projede yaptığım şey bu:

Kullanımı:

Bugünün GERÇEK tarihini al Saat

var today = SystemTime.Now().Date;

DateTime.Now yerine kullanmak zorundasınız SystemTime.Now()... Zor bir değişiklik değil ama bu çözüm tüm projeler için ideal olmayabilir.

Zamanda Yolculuk (Gelecekte 5 yıl gidelim)

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

Sahte Olsun "bugün" ("bugün" den 5 yıl sonra)

var fakeToday = SystemTime.Now().Date;

Tarihi sıfırla

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
Teşekkür ederim, bu fonksiyonel yaklaşımı seviyorum. Bugün (iki yıl sonra) Hala TimeProvider sınıfının değiştirilmiş bir sürümünü kullanıyorum (kabul edilen cevabı kontrol edin), gerçekten güzel çalışıyor.
Pedro

7
@crabCRUSHERclamCOLI: Eğer fikri ayende.com/blog/3408/dealing-with-time-in-tests adresinden edindiyseniz , bağlantı kurmak iyi bir şeydir.
Johann Gerell

3
Bu kavram aynı zamanda Guid nesil alay etmek için de harika çalışıyor (genel statik Func <Guid> NewGuid = () => Guid.NewGuid ();
mdwhatcott

2
Bu yararlı yöntemi de ekleyebilirsiniz: public static void ResetDateTime (DateTime dateTimeNow) {var timespanDiff = TimeSpan.FromTicks (DateTime.Now.Ticks - dateTimeNow.Ticks); Şimdi = () => DateTime.Now - timespanDiff; }
Pavel Hodek

17
Paralel çalışan diğer birim testlerini etkileyebilir.
Amete Blessed

24

Benler:

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

Feragatname - Ben Moles üzerinde çalışıyorum


1
maalesef nunit ve yeniden şekillendirici ile çalışmaz.
odyth

Görünüşe göre Moles artık desteklenmiyor, yerine Fakes msdn.microsoft.com/en-us/library/… , şüphesiz MS bunu çok yakında
durduracak

17

Bunu yapmak için bazı seçenekleriniz var:

  1. Alaycı çerçeve kullanın ve bir DateTimeService kullanın (Küçük bir sarmalayıcı sınıfı uygulayın ve üretim koduna enjekte edin). Sarıcı uygulaması DateTime'a erişecek ve testlerde sarıcı sınıfını taklit edebileceksiniz.

  2. Typemock İzolatörünü kullanın , DateTime'ı taklit edebilir.Şimdi ve test edilen kodu değiştirmenizi gerektirmez.

  3. Mol kullanın , aynı zamanda DateTime sahte olabilir.Şimdi ve üretim kodunda değişiklik gerektirmez.

Bazı örnekler:

Adedi kullanarak sarıcı sınıfı:

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

Taklit DateTime doğrudan İzolatör kullanarak:

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

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

Feragatname - Typemock'ta çalışıyorum


12

Sistem için sahte bir montaj ekleyin (Sistem referansı => Sahte montaj ekle'ye sağ tıklayın).

Ve test yönteminize yazın:

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

Vs Premium veya ultimate'a erişiminiz varsa Bu, bunu yapmanın en kolay / en hızlı yoludur.
Rugdr

7

@Crabcrusherclamcollector yanıtı ile ilgili olarak, EF sorgularında bu yaklaşımı kullanırken sorun vardır (System.NotSupportedException: 'Invoke' LINQ ifade düğümü türü 'Invoke' LINQ to Entities'de desteklenmez). Uygulamayı şu şekilde değiştirdim:

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 tarafından tahmin ediyorum Entity Framework? SystemTime'ı kullandığınız nesne neye benziyor? Benim tahminim gerçek varlık üzerinde SystemTime referans var, bunun yerine varlığınızda normal bir DateTime nesnesi kullanmanız ve uygulamanızda mantıklı olduğu yerde SystemTime kullanarak ayarlamanız gerekir
crabCRUSHERclamCOLLECTOR 26:03

1
Evet linq sorgusu ve EntityFramework6 oluştururken ve SystemTime bu sorgu UtcNow başvuru sırasında test ettim. Açıklandığı gibi bir istisna vardı. Uygulamayı değiştirdikten sonra iyi çalışıyor.
marcinn

Bu çözümü seviyorum! Harika!
mirind4

7

Bağlı olan bir kod sınamak için System.DateTime, system.dllalay gerekmektedir.

Bunu yaptığını bildiğim iki çerçeve var. Microsoft sahte ve Smocks .

Microsoft sahte görsel stüdyo 2012 ültimatom gerektirir ve doğrudan compton dışında çalışır.

Smocks açık kaynaklı ve kullanımı çok kolay. NuGet kullanılarak indirilebilir.

Aşağıdaki bir sahte gösterir System.DateTime:

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

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

5

Bir iş parçacığı güvenli SystemClockkullanarak ThreadLocal<T>benim için harika çalışıyor.

ThreadLocal<T> .Net Framework v4.0 ve sonraki sürümlerinde kullanılabilir.

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

Kullanım örneği:

[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
+! Yerel iş parçacığı, geçerli Nowörneği geçersiz kılan paralel test yürütme ve çoklu testlerle ilgili sorunlardan kaçınmalıdır .
Hux

1
Bunu kullanmaya çalıştım ama iş parçacıkları arasında bir sorun vardı, bu yüzden benzer bir prensip ile gitti ama AsyncLocalyerine kullanarakThreadLocal
rrrr

@rrrr: Yaşadığınız sorunu açıklayabilir misiniz?
Henk van Boeijen

@HenkvanBoeijen Elbette, zamanın ayarlandığı zaman sorunlarla karşılaştık ve daha sonra zaman uyumsuz bir yöntem bekliyorduk. 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 Ben link
rrrr

3

Aynı sorunla karşılaştım, ancak Microsoft'tan bu sorunu çözen bir araştırma projesi buldum.

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

Moles, .NET'te delegelere dayanan test saplamaları ve sapmalar için hafif bir çerçevedir. Moller, mühürlü tiplerde sanal olmayan / statik yöntemler de dahil olmak üzere herhangi bir .NET yöntemini dolamak için kullanılabilir

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

Örnek kod orijinalden değiştirildi.

Ben bir sağlayıcı içine DateTime önerilen ve soyutlanan ne yapmıştı. Sadece yanlış hissettirdi ve sadece test için çok fazla olduğunu hissettim. Bunu bu akşam kişisel projeme uygulayacağım.


2
BTW, bu sadece VS2010 ile çalışır. Moles'in VS2012 için şimdi Fakes olduğunu bulduğumda üzüldüm ve sadece Premium ve Ultimate Visual Studios için kullanılabilir. VS 2012 Professional için Sahte Yok. Yani bunu asla kullanmam gerekmedi. :(
Bobby Cannon

3

Alay üzerinde bir özel not DateTime.Nowile Typemock ...

Bunun DateTime.Nowdüzgün şekilde alay edilebilmesi için değeri bir değişkene yerleştirilmelidir. Örneğin:

Bu çalışmıyor:

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

Ancak, bu şunları yapar:

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

2

Sahte nesneler.

Testiniz için uygun bir Şimdi döndüren sahte bir DateTime.


2

Kimsenin gitmenin en belirgin yollarından birini önermediğine şaşırdım:

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

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

Daha sonra test çiftinizde bu yöntemi geçersiz kılabilirsiniz.

Ayrıca TimeProviderbazı durumlarda bir sınıf enjekte etmek gibi , ama diğerleri için bu fazlasıyla yeterli. TimeProviderGerçi bunu birkaç sınıfta tekrar kullanmanız gerekiyorsa muhtemelen sürümü tercih ederim .

DÜZENLEME: İlgilenen herkes için buna sınıfınıza bir "dikiş" eklemek denir, burada sınıftaki kodu değiştirmek zorunda kalmadan onu değiştirmek için (test amacıyla veya başka bir şekilde) davranışına bağlanabilirsiniz.


1

DateTimeProvider IDisposable'ı uyguladığında iyi bir uygulamadır.

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

Ardından, birim testleri için sahte DateTime'ınızı enjekte edebilirsiniz.

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

Örnek bakınız DateTimeProvider Örneği


1

Bunu yapmanın temiz bir yolu VirtualTime enjekte etmektir. Zamanı kontrol etmenizi sağlar. İlk önce VirtualTime'ı kurun

Install-Package VirtualTime

Bu, örneğin, DateTime'a yapılan tüm çağrılarda 5 kat daha hızlı hareket etmesine izin verir.

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

Zamanın daha yavaş hareket etmesini sağlamak için, örneğin 5 kat daha yavaş

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

Zamanı ayakta tutmak için

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

Zamanda geriye gitmek henüz test edilmedi

İşte bir örnek 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));
}

Daha fazla teste buradan göz atabilirsiniz:

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

DateTime.Now.ToVirtualTime uzantısının size verdiği şey, ITime'a bağlı bir yönteme / sınıfa ilettiğiniz bir ITime örneğidir. bazı DateTime.Now.ToVirtualTime seçtiğiniz DI kapsayıcısında ayarlandı

Sınıf kontrüstoruna enjekte edilen başka bir örnek

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

Ayende tarafından DateTime'a dayanan test kodu hakkında da iyi bir okuma var burada ayende.com/blog/3408/dealing-with-time-in-tests
Samuel

1

Statik bir SystemTime nesnesi kullanıyorduk, ancak paralel birim testlerini çalıştırırken sorunlarla karşılaştık. Henk van Boeijen'in çözümünü kullanmaya çalıştım, ancak async evreli iplikler arasında sorunlar yaşadım, AsyncLocal'ı aşağıdakine benzer bir şekilde kullandım:

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 kaynağından alınmıştır


1

Eski bir soru, ama yine de geçerli.

Benim yaklaşımım, System.DateTime.Nowaramayı sarmak için yeni bir arayüz ve sınıf oluşturmak

public interface INow
{
    DateTime Execute();
}

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

Bu arayüz, geçerli tarih ve saati alması gereken herhangi bir sınıfa enjekte edilebilir. Bu örnekte, geçerli tarih ve saate bir zaman aralığı ekleyen bir sınıfım var (bir birim test edilebilir 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);
    }
}

Doğru çalıştığından emin olmak için testler yazılabilir. NUnit ve Moq kullanıyorum ama herhangi bir test çerçevesi

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

Bu desen gibi dış faktörlere bağlıdır herhangi fonksiyonlar için çalışacak System.Random.Next(), System.DateTime.Now.UtcNow, System.Guid.NewGuid()vb

Daha fazla örnek için https://loadlimited.visualstudio.com/Stamina/_git/Stamina.Core adresine bakın veya https://www.nuget.org/packages/Stamina.Core nuget paketini edinin .


1

Test etmekte olduğunuz sınıfı Func<DateTime>yapıcı parametreleri üzerinden geçirilecek bir sınıfı değiştirebilir , böylece sınıfın gerçek kodda örneğini oluşturduğunuzda () => DateTime.UtcNow, Func<DateTime>parametreye geçebilir ve testte, test etmek istiyor.

Örneğin:

    [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
Bu cevabı beğendim. Yine de topladığımdan, bu, sınıflara / nesnelere daha fazla odaklanılan C # dünyasında göz atma olabilir. Her iki durumda da, bunu tercih ederim çünkü neler olduğunu bana göre çok basit ve açık.
pseudoramble

0

Aynı problemim var, ama set datetime şeylerini aynı sınıfta kullanmamamız gerektiğini düşünüyordum. çünkü bir gün yanlış kullanıma yol açabilir. bu yüzden sağlayıcıyı kullandım

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

Testler için, test projesinde belirlenen şeylerle ilgilenecek bir yardımcı yaptı,

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

kodda

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

ve testlerde

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

Ancak bir şeyi hatırlamanız gerekir, bazen gerçek DateTime ve sağlayıcının DateTime işlevi aynı değildir

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

Gecikmenin maksimum TimeSpan olacağını varsaydım . Milisaniye (0.00002) . Ama çoğu zaman daha da az

MockSamples'ta örneği bulun


0

İşte bu sorunun cevabı. 'Ortam Bağlamı' desenini IDisposable ile birleştiriyorum. Böylece DateTimeProvider.Current'ı normal program kodunuzda kullanabilirsiniz ve testte kapsamı bir using ifadesiyle geçersiz kılabilirsiniz.

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

UnitTest içinde yukarıdaki DateTimeProvider'ı nasıl kullanacağınız aşağıda açıklanmıştır

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

ITimeProviderBunu kullanarak , diğer projelerin geri kalanından referans alınması gereken özel ortak ortak projeye götürmek zorunda kaldık . Ancak bu , bağımlılıkların kontrolünü karmaşıklaştırdı .

ITimeProvider.NET çerçevesinde arama yaptık . NuGet paketini araştırdık ve çalışamayan bir paket bulduk DateTimeOffset.

Bu yüzden, sadece standart kütüphanenin türlerine bağlı olan kendi çözümümüzü bulduk. Örneğini kullanıyoruz Func<DateTimeOffset>.

Nasıl kullanılır

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

Nasıl kayıt olunur

Autofac

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

( Gelecek editörler için: Vakalarınızı buraya ekleyin ).

Test birimi nasıl yapılır

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

Belki daha az Profesyonel ama daha basit bir çözüm tüketici yönteminde bir DateTime parametresi yapmak olabilir.Örneğin, SampleMethod gibi bir yöntem yapmak yerine, Parametreli SampleMethod1 yapın. SampleMethod1'in test edilmesi daha kolaydır

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

        }

    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.