Unit Testing: DateTime.Now

164

Saya memiliki beberapa unit test yang mengharapkan 'waktu saat ini' berbeda dari DateTime. Sekarang dan saya tidak ingin mengubah waktu komputer, jelas.

Apa strategi terbaik untuk mencapai ini?

Pedro
sumber
menyimpan beberapa nilai lama di waktu sekarang dan membandingkannya dengan datetime. Sekarang, karena pengujian Unit menggunakan data palsu, Anda dapat dengan mudah melakukan itu, bukan?
Prashant Lakhlani
3
Menyediakan abstraksi DateTime saat ini berguna tidak hanya untuk pengujian dan debug tetapi juga dalam kode produksi - itu tergantung pada kebutuhan aplikasi.
Pavel Hodek
1
Jangan gunakan kelas statis untuk mendapatkan DateTime
Daniel Little
dan pendekatan sederhana lain menggunakan VirtualTime di bawah ini
Samuel
Salah satu opsi yang mungkin berfungsi dan Anda dapat menambahkan kelebihan adalah untuk membuat kelebihan metode yang menerima DateTime. Ini akan menjadi yang Anda uji, metode yang ada hanya akan memanggil kelebihan beban now.
Phil Cooper

Jawaban:

218

The terbaik strategi untuk membungkus waktu saat ini dalam abstraksi dan menyuntikkan abstraksi yang ke konsumen .


Atau , Anda juga dapat menentukan abstraksi waktu sebagai Konteks Sekitar :

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

Ini akan memungkinkan Anda untuk mengkonsumsinya seperti ini:

var now = TimeProvider.Current.UtcNow;

Dalam tes unit, Anda bisa mengganti TimeProvider.Currentdengan objek Uji Ganda / Mock. Contoh menggunakan Moq:

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

Namun, ketika pengujian unit dengan keadaan statis, selalu ingat untuk merobohkan perlengkapan Anda dengan menelepon TimeProvider.ResetToDefault().

Mark Seemann
sumber
7
Dalam kasus konteks ambient, bahkan jika Anda benar-benar hancur, bagaimana Anda membuat tes Anda berjalan secara paralel?
Ilya Chernomordik
2
@IlyaChernomordik Anda tidak harus menyuntikkan ILogger atau Masalah Lintas Sektoral lainnya , jadi menyuntikkan penyedia waktu masih merupakan pilihan terbaik.
Mark Seemann
5
@ MikeK Seperti kalimat pertama dalam jawaban saya mengatakan: Strategi terbaik adalah membungkus waktu saat ini dalam abstraksi dan menyuntikkan abstraksi itu ke konsumen. Segala sesuatu yang lain dalam jawaban ini adalah alternatif terbaik kedua.
Mark Seemann
3
Hai. I Noob. Bagaimana cara mencegah orang mengakses DateTime.UtcNow dengan solusi ini? Akan mudah bagi seseorang untuk ketinggalan menggunakan TimeProvider. Jadi mengarah ke bug dalam pengujian?
Obbles
1
Ulasan Kode @Obbles (atau pemrograman pasangan, yang merupakan bentuk ulasan kode langsung). Saya kira orang bisa menulis alat yang memindai basis kode DateTime.UtcNowdan sejenisnya, tetapi ulasan kode adalah ide yang bagus.
Mark Seemann
58

Ini semua adalah jawaban yang baik, inilah yang saya lakukan pada proyek yang berbeda:

Pemakaian:

Dapatkan Tanggal NYATA hari ini

var today = SystemTime.Now().Date;

Alih-alih menggunakan DateTime. Sekarang, Anda perlu menggunakan SystemTime.Now()... Ini bukan perubahan yang sulit tetapi solusi ini mungkin tidak ideal untuk semua proyek.

Time Travelling (Ayo 5 tahun ke depan)

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

Dapatkan "Hari Ini" Palsu kami (akan 5 tahun dari 'hari ini')

var fakeToday = SystemTime.Now().Date;

Setel ulang tanggal

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
sumber
1
Terima kasih, saya suka pendekatan fungsional ini. Hari ini (dua tahun kemudian) saya masih menggunakan versi modifikasi dari kelas TimeProvider (periksa jawaban yang diterima), itu berfungsi dengan sangat baik.
Pedro
7
@ crabCRUSHERclamCOLI: Jika Anda mendapat ide dari ayende.com/blog/3408/dealing-with-time-in-tests , maka A Good Thing untuk menautkannya.
Johann Gerell
3
Konsep ini juga berfungsi baik untuk mengejek generasi Guid (Fungsi statis publik <Guid> NewGuid = () => Guid.NewGuid ();
mdwhatcott
2
Anda juga dapat menambahkan metode yang berguna ini: public static void ResetDateTime (DateTime dateTimeNow) {var timespanDiff = TimeSpan.FromTicks (DateTime.Now.Ticks - dateTimeNow.Ticks); Sekarang = () => DateTime.Now - timespanDiff; }
Pavel Hodek
17
sangat berbahaya. Ini dapat memengaruhi tes unit lain yang berjalan paralel.
Amete Blessed
24

Tahi lalat:

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

Penafian - Saya mengerjakan Moles

Peli
sumber
1
sayangnya tidak bekerja dengan nunit dan resharper.
Oktober
Sepertinya Moles sekarang tidak didukung, diganti dengan Fakes msdn.microsoft.com/en-us/library/… , tidak diragukan lagi MS akan menghentikannya terlalu cepat
danio
17

Anda memiliki beberapa opsi untuk melakukannya:

  1. Gunakan kerangka kerja mengejek dan gunakan DateTimeService (Menerapkan kelas pembungkus kecil dan menyuntikkannya ke kode produksi). Implementasi wrapper akan mengakses DateTime dan dalam tes Anda akan dapat mengejek kelas wrapper.

  2. Gunakan Typemock Isolator , itu bisa memalsukan DateTime. Sekarang dan tidak akan mengharuskan Anda untuk mengubah kode yang sedang diuji.

  3. Gunakan Moles , itu juga bisa memalsukan DateTime. Sekarang dan tidak akan memerlukan perubahan dalam kode produksi.

Beberapa contoh:

Kelas wrapper menggunakan 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; } }
}

Memalsukan DateTime secara langsung menggunakan Isolator:

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

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

Penafian - Saya bekerja di Typemock

Elisa
sumber
12

Tambahkan perakitan palsu untuk Sistem (klik kanan pada Referensi sistem => Tambahkan perakitan palsu).

Dan tulis ke dalam metode pengujian Anda:

using (ShimsContext.Create())
{
   System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10);
   MethodThatUsesDateTimeNow();
}
dave
sumber
jika Anda memiliki akses ke Vs Premium atau ultimate Ini adalah cara termudah / tercepat untuk melakukannya.
Rugdr
7

Mengenai jawaban @crabcrusherclamcollector, ada masalah saat menggunakan pendekatan itu dalam kueri EF (System.NotSupportedException: Node ekspresi LINQ tipe 'Invoke' tidak didukung di LINQ to Entities). Saya memodifikasi implementasi menjadi:

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
sumber
Saya menduga dengan EF maksud Anda Kerangka Entitas? Seperti apa objek yang Anda gunakan pada SystemTime? Dugaan saya adalah Anda memiliki referensi ke SystemTime pada entitas aktual, alih-alih Anda harus menggunakan objek DateTime reguler pada entitas Anda dan mengaturnya menggunakan SystemTime di mana pun masuk akal dalam aplikasi Anda
crabCRUSHERclamCOLLECTOR
1
Ya, saya telah mengujinya saat membuat kueri linq dan EntityFramework6 dan merujuk pada kueri itu UtcNow dari SystemTime. Ada pengecualian seperti yang dijelaskan. Setelah mengubah implementasi itu berfungsi dengan baik.
marcinn
Saya suka solusi ini! Bagus!
mirind4
7

Untuk menguji kode yang bergantung pada System.DateTime,system.dll harus diejek.

Ada dua kerangka kerja yang saya tahu melakukan hal ini. Microsoft palsu dan Smocks .

Microsoft palsu memerlukan visual studio 2012 ultimatum dan langsung bekerja dari compton.

Smocks adalah sumber terbuka dan sangat mudah digunakan. Itu dapat diunduh menggunakan NuGet.

Berikut ini menunjukkan tiruan dari System.DateTime :

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

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

Utas aman SystemClockdigunakan ThreadLocal<T>sangat bagus untuk saya.

ThreadLocal<T> tersedia di .Net Framework v4.0 dan lebih tinggi.

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

Contoh penggunaan:

[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
sumber
1
+! Utas lokal harus menghindari masalah dengan pelaksanaan tes paralel dan beberapa tes berpotensi mengesampingkan Nowinstance saat ini .
Hux
1
Sudah mencoba menggunakan ini tetapi memiliki masalah di utas jadi telah pergi dengan prinsipal yang sama tetapi menggunakan AsyncLocaldaripadaThreadLocal
rrrr
@ rrrr: dapatkah Anda menjelaskan masalah yang Anda alami?
Henk van Boeijen
@HenkvanBoeijen Tentu, kami mengalami masalah ketika waktu ditetapkan dan kemudian kami sedang menunggu metode async. 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 Saya telah menambahkan jawaban dengan apa yang akhirnya saya gunakan tautan
rrrr
3

Saya mengalami masalah yang sama tetapi menemukan proyek penelitian dari Microsoft yang memecahkan masalah ini.

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

Tahi lalat adalah kerangka kerja ringan untuk rintisan pengujian dan jalan memutar di .NET yang didasarkan pada delegasi. Tahi lalat dapat digunakan untuk memutar metode .NET, termasuk metode non-virtual / statis dalam tipe tertutup

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

Kode sampel telah dimodifikasi dari aslinya.

Saya telah melakukan apa yang disarankan orang lain dan mengabstraksi DateTime menjadi penyedia. Rasanya salah dan saya merasa terlalu banyak hanya untuk pengujian. Saya akan menerapkan ini ke dalam proyek pribadi saya malam ini.

Bobby Cannon
sumber
2
BTW, ini hanya bekerja dengan VS2010. Saya kesal ketika saya menemukan bahwa Moles sekarang Palsu untuk VS2012 dan hanya tersedia untuk Premium dan Ultimate Visual Studios. Tidak Palsu untuk VS 2012 Professional. Jadi saya tidak pernah benar-benar menggunakan ini. :(
Bobby Cannon
3

Satu catatan khusus tentang mengejek DateTime.Nowdengan TypeMock ...

Nilai DateTime.Nowharus ditempatkan ke dalam variabel agar dapat diejek dengan benar. Sebagai contoh:

Ini tidak bekerja:

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

Namun, ini tidak:

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

Objek Mock.

DateTime tiruan yang mengembalikan Sekarang yang sesuai untuk pengujian Anda.

S.Lott
sumber
2

Saya terkejut tidak ada yang menyarankan salah satu cara yang paling jelas:

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

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

Kemudian Anda dapat dengan mudah menimpa metode ini dalam tes ganda Anda.

Saya juga agak suka menyuntikkan TimeProviderkelas dalam beberapa kasus, tetapi untuk yang lain, ini lebih dari cukup. Saya mungkin lebih menyukai TimeProviderversi ini jika Anda perlu menggunakan kembali ini di beberapa kelas.

EDIT: Bagi siapa pun yang tertarik, ini disebut menambahkan "jahitan" ke kelas Anda, titik di mana Anda dapat menghubungkan ke perilaku itu untuk memodifikasinya (untuk tujuan pengujian atau sebaliknya) tanpa benar-benar harus mengubah kode di kelas.

sara
sumber
1

Praktik yang baik adalah, ketika DateTimeProvider mengimplementasikan 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; 
    } 
} 

Selanjutnya, Anda dapat menyuntikkan DateTime palsu Anda untuk pengujian unit

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

Lihat contoh Contoh DateTimeProvider

Mino
sumber
1

Salah satu cara bersih untuk melakukan ini adalah dengan menyuntikkan VirtualTime. Ini memungkinkan Anda untuk mengontrol waktu. Pertama instal VirtualTime

Install-Package VirtualTime

Itu memungkinkan, misalnya, membuat waktu yang bergerak 5 kali lebih cepat pada semua panggilan ke DateTime. Sekarang atau UtcNow

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

Untuk membuat waktu bergerak lebih lambat, mis. 5 kali lebih lambat

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

Untuk membuat waktu tetap berdiri lakukan

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

Bergerak mundur dalam waktu belum diuji

Berikut ini adalah contoh tes:

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

Anda dapat memeriksa lebih banyak tes di sini:

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

Apa yang diberikan DateTime.Now.ToVirtualTime adalah instance ITime yang Anda berikan ke metode / kelas yang bergantung pada ITime. beberapa DateTime.Now.ToVirtualTime diatur dalam wadah DI pilihan Anda

Berikut adalah contoh lain menyuntikkan ke kelas contrustor

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
sumber
Ada juga bacaan yang bagus tentang kode pengujian yang bergantung pada DateTime oleh Ayende di sini ayende.com/blog/3408/dealing-with-time-in-tests
Samuel
1

Kami menggunakan objek SystemTime statis, tetapi mengalami masalah menjalankan tes unit paralel. Saya mencoba menggunakan solusi Henk van Boeijen tetapi memiliki masalah di thread asynchronous yang muncul, akhirnya menggunakan AsyncLocal dengan cara yang mirip dengan di bawah ini:

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

Bersumber dari https://gist.github.com/CraftyFella/42f459f7687b0b8b268fc311e6b4af08

rrrr
sumber
1

Sebuah pertanyaan lama, tetapi masih valid.

Pendekatan saya adalah membuat antarmuka dan kelas baru untuk menutup System.DateTime.Nowpanggilan

public interface INow
{
    DateTime Execute();
}

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

Antarmuka ini dapat disuntikkan ke kelas mana saja yang perlu mendapatkan tanggal dan waktu saat ini. Dalam contoh ini, saya memiliki kelas yang menambahkan rentang waktu ke tanggal dan waktu saat ini (sebuah unit yang dapat diuji 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);
    }
}

Dan tes dapat ditulis untuk memastikan bahwa itu berfungsi dengan benar. Saya menggunakan NUnit dan Moq tetapi kerangka uji apa pun akan dilakukan

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

Pola ini akan bekerja untuk setiap fungsi yang tergantung pada faktor-faktor eksternal, seperti System.Random.Next(), System.DateTime.Now.UtcNow, System.Guid.NewGuid()dll

Lihat https://loadlimited.visualstudio.com/Stamina/_git/Stamina.Core untuk contoh lebih lanjut atau dapatkan https://www.nuget.org/packages/Stamina.Core paket nuget.

Kevin Brydon
sumber
1

Anda dapat mengubah kelas yang Anda uji untuk menggunakan Func<DateTime>yang akan melewati parameter konstruktornya, jadi ketika Anda membuat instance dari kelas dalam kode nyata, Anda bisa meneruskan () => DateTime.UtcNowkeFunc<DateTime> parameter, dan pada tes, Anda bisa menghabiskan waktu Anda ingin menguji.

Sebagai contoh:

    [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
sumber
1
Saya suka jawaban ini. Dari apa yang telah saya kumpulkan, ini mungkin adalah pengangkatan alis mata di dunia C # di mana ada lebih banyak fokus pada kelas / objek. Apa pun itu, saya lebih suka ini karena sangat sederhana dan jelas bagi saya apa yang terjadi.
pseudoramble
0

Saya mendapat masalah yang sama, tetapi saya berpikir kita tidak boleh menggunakan set datetime hal-hal di kelas yang sama. karena bisa mengakibatkan penyalahgunaan suatu hari nanti. jadi saya telah menggunakan provider seperti

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

Untuk tes, pada proyek uji dibuat penolong yang akan berurusan dengan hal-hal yang ditetapkan,

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

pada kode

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

dan pada tes

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

Tetapi perlu mengingat satu hal, kadang-kadang DateTime yang asli dan DateTime penyedia tidak bertindak sama

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

Saya berasumsi penghormatan akan menjadi TimeSpan.FromMilliseconds maksimum (0,00002) . Tetapi sebagian besar waktu bahkan lebih sedikit

Temukan sampel di MockSamples

Dipon Roy
sumber
0

Ini jawaban saya untuk pertanyaan ini. Saya menggabungkan pola 'Ambient Context' dengan IDisposable. Jadi Anda bisa menggunakan DateTimeProvider.Current dalam kode program normal Anda dan dalam tes Anda menimpa ruang lingkup dengan pernyataan menggunakan.

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

Berikut adalah cara menggunakan DateTimeProvider di atas di dalam Unit-Test

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

Menggunakan ITimeProviderkami terpaksa membawanya ke proyek bersama umum khusus yang harus dirujuk dari sisa proyek lainnya. Tetapi ini mempersulit kontrol ketergantungan .

Kami mencari ITimeProviderdi .NET framework. Kami mencari paket nuget, dan menemukan satu yang tidak bisa bekerja dengan DateTimeOffset.

Jadi kami datang dengan solusi kami sendiri, yang hanya bergantung pada jenis perpustakaan standar. Kami menggunakan contoh dari Func<DateTimeOffset>.

Cara Penggunaan

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

Bagaimana cara mendaftar

Autofac

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

( Untuk editor mendatang: tambahkan kasus Anda di sini ).

Cara tes unit

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

Mungkin kurang Profesional tetapi solusi yang lebih sederhana dapat membuat parameter DateTime di metode konsumen. Misalnya alih-alih membuat metode seperti SampleMethod, buat SampleMethod1 dengan parameter. Menguji SampleMethod1 lebih mudah

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
sumber