Kiểm tra đơn vị: DateTime. Bây giờ


164

Tôi có một số bài kiểm tra đơn vị dự kiến ​​"thời gian hiện tại" sẽ khác với DateTime. Rõ ràng và tôi không muốn thay đổi thời gian của máy tính.

Chiến lược tốt nhất để đạt được điều này là gì?


lưu trữ một số giá trị cũ trong thời điểm hiện tại và so sánh nó với datetime.now, vì Kiểm thử đơn vị sử dụng dữ liệu giả mạo, bạn có thể dễ dàng làm điều đó không?
Prashant Lakhlani

3
Việc cung cấp sự trừu tượng của DateTime hiện tại rất hữu ích không chỉ để kiểm tra và gỡ lỗi mà còn trong mã sản xuất - nó phụ thuộc vào nhu cầu ứng dụng.
Pavel Hodek

1
Không sử dụng lớp tĩnh để lấy DateTime
Daniel Little

và một cách tiếp cận đơn giản khác bằng cách sử dụng VirtualTime bên dưới
Samuel

Một tùy chọn có thể hoạt động và bạn có thể thêm quá tải là tạo quá tải phương thức chấp nhận DateTime. Đây sẽ là một trong những bạn kiểm tra, phương thức hiện có chỉ đơn giản là gọi quá tải now.
Phil Cooper

Câu trả lời:


218

Các tốt nhất chiến lược là quấn thời gian hiện tại trong một sự trừu tượng và tiêm trừu tượng đó vào người tiêu dùng .


Ngoài ra , bạn cũng có thể định nghĩa một sự trừu tượng hóa thời gian là Bối cảnh xung quanh :

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

Điều này sẽ cho phép bạn tiêu thụ nó như thế này:

var now = TimeProvider.Current.UtcNow;

Trong một bài kiểm tra đơn vị, bạn có thể thay thế TimeProvider.Currentbằng một đối tượng Test Double / Mock. Ví dụ sử dụng Moq:

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

Tuy nhiên, khi kiểm tra đơn vị với trạng thái tĩnh, luôn nhớ xé đồ đạc của bạn bằng cách gọi TimeProvider.ResetToDefault().


7
Trong trường hợp bối cảnh xung quanh, ngay cả khi bạn phá hỏng, làm thế nào để bạn thực hiện các bài kiểm tra của mình chạy song song?
Ilya Chernomordik

2
@IlyaCécomordik Bạn không cần phải tiêm ILogger hoặc các mối quan tâm xuyên suốt khác , vì vậy tiêm một nhà cung cấp thời gian vẫn là lựa chọn tốt nhất.
Mark Seemann

5
@MikeK Như câu đầu tiên trong câu trả lời của tôi nói: Chiến lược tốt nhất là bao bọc thời gian hiện tại một cách trừu tượng và tiêm sự trừu tượng đó vào người tiêu dùng. Tất cả mọi thứ khác trong câu trả lời này là một sự thay thế tốt thứ hai.
Mark Seemann

3
Chào. Tôi trẩu. Làm cách nào để ngăn mọi người truy cập DateTime.UtcNow với giải pháp này? Sẽ rất dễ để ai đó bỏ lỡ khi sử dụng TimeProvider. Như vậy dẫn đến lỗi trong thử nghiệm?
Trở ngại

1
@Obble Đánh giá mã (hoặc lập trình cặp, là một hình thức đánh giá mã trực tiếp). Tôi cho rằng người ta có thể viết một công cụ quét cơ sở mã cho DateTime.UtcNowvà tương tự, nhưng đánh giá mã dù sao cũng là một ý tưởng tốt.
Mark Seemann

58

Đây đều là những câu trả lời hay, đây là những gì tôi đã làm trong một dự án khác:

Sử dụng:

Nhận ngày hôm nay Thời gian thực

var today = SystemTime.Now().Date;

Thay vì sử dụng DateTime SystemTime.Now(). Bây giờ, bạn cần sử dụng ... Đó không phải là thay đổi khó khăn nhưng giải pháp này có thể không lý tưởng cho tất cả các dự án.

Du hành thời gian (Hãy đi 5 năm trong tương lai)

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

Nhận "Fake" của chúng tôi (sẽ là 5 năm kể từ "hôm nay")

var fakeToday = SystemTime.Now().Date;

Đặt lại ngày

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
Cảm ơn bạn, tôi thích aproach chức năng này. Hôm nay (hai năm sau) Tôi vẫn đang sử dụng một phiên bản sửa đổi của lớp TimeProvider (kiểm tra câu trả lời được chấp nhận), nó hoạt động thực sự tốt.
Pedro

7
@crabCRUSHERclamCOLI: Nếu bạn có ý tưởng từ ayende.com/blog/3408/deals-with-time-in-tests , thì đó là một điều tốt để liên kết với nó.
Johann Gerell

3
Khái niệm này cũng hoạt động rất tốt để chế nhạo thế hệ Guid (công khai Func <Guid> NewGuid = () => Guid.NewGuid ();
mdwhatcott

2
Bạn cũng có thể thêm phương thức hữu ích này: public static void ResetDateTime (DateTime dateTimeNow) {var timespanDiff = TimeSpan.FromTicks (DateTime.Now.Ticks - dateTimeNow.Ticks); Bây giờ = () => DateTime. Bây giờ - timespanDiff; }
Pavel Hodek

17
rất nguy hiểm. Điều này có thể ảnh hưởng đến các bài kiểm tra đơn vị khác chạy song song.
Amete may mắn

24

Nốt ruồi

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

Tuyên bố miễn trừ trách nhiệm - Tôi làm việc về nốt ruồi


1
Thật không may, không làm việc với nunit và chia sẻ lại.
odyth

Có vẻ như Moles hiện không được hỗ trợ, được thay thế bằng Fakes msdn.microsoft.com/en-us/l Library / , không nghi ngờ gì nữa, MS sẽ ngừng điều đó quá sớm
danio

17

Bạn có một số tùy chọn để làm điều đó:

  1. Sử dụng khung mô phỏng và sử dụng DateTimeService (Thực hiện một lớp bao bọc nhỏ và đưa nó vào mã sản xuất). Việc triển khai trình bao bọc sẽ truy cập DateTime và trong các thử nghiệm, bạn sẽ có thể giả định lớp trình bao bọc.

  2. Sử dụng Bộ cách ly typemock , nó có thể giả mạo DateTime. Bây giờ và sẽ không yêu cầu bạn thay đổi mã đang thử nghiệm.

  3. Sử dụng nốt ruồi , nó cũng có thể giả mạo DateTime. Bây giờ và sẽ không yêu cầu thay đổi mã sản xuất.

Vài ví dụ:

Lớp Wrapper sử dụng 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; } }
}

Làm giả DateTime trực tiếp bằng Isolator:

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

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

Tuyên bố miễn trừ trách nhiệm - Tôi làm việc tại typemock


12

Thêm lắp ráp giả cho Hệ thống (nhấp chuột phải vào Tham chiếu hệ thống => Thêm lắp ráp giả).

Và viết vào phương pháp kiểm tra của bạn:

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

nếu bạn có quyền truy cập vào Vs Premium hoặc cuối cùng thì đây là cách dễ nhất / nhanh nhất để làm điều đó.
Rugdr

7

Liên quan đến câu trả lời @crabcrusherclamcollector, có một vấn đề khi sử dụng phương pháp đó trong các truy vấn EF (System.NotSupportedException: Loại nút biểu thức LINQ 'Gọi' không được hỗ trợ trong LINQ to Entities). Tôi đã sửa đổi thực hiện theo đó:

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

Tôi đoán bởi EF bạn có nghĩa là Entity Framework? Đối tượng bạn đang sử dụng SystemTime trông như thế nào? Tôi đoán là bạn có một tham chiếu đến SystemTime trên thực thể thực tế, thay vào đó bạn nên sử dụng một đối tượng DateTime thông thường trên thực thể của mình và đặt nó bằng SystemTime bất cứ nơi nào có ý nghĩa trong ứng dụng của bạn
CrabCRUSHERclamCOLLECTOR

1
Có, tôi đã kiểm tra nó khi tạo truy vấn linq và EntityFramework6 và tham chiếu trong truy vấn đó UtcNow từ SystemTime. Có ngoại lệ như mô tả. Sau khi thay đổi thực hiện nó hoạt động tốt.
marcinn

Tôi thích giải pháp này! Tuyệt quá!
mirind4

7

Để kiểm tra một mã phụ thuộc vào System.DateTime, system.dllphải được chế giễu.

Có hai khuôn khổ mà tôi biết về việc này. Microsoft giả mạoSmocks .

Microsoft giả mạo yêu cầu tối hậu thư phòng thu 2012 và hoạt động ra khỏi compton.

Smocks là một nguồn mở và rất dễ sử dụng. Nó có thể được tải xuống bằng NuGet.

Sau đây cho thấy một giả System.DateTime:

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

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

5

Một chủ đề an toàn SystemClocksử dụng ThreadLocal<T>làm việc tuyệt vời cho tôi.

ThreadLocal<T> có sẵn trong .Net Framework v4.0 trở lên.

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

Ví dụ sử dụng:

[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
+! Chủ đề cục bộ nên tránh các vấn đề với thực thi kiểm tra song song và nhiều kiểm tra có khả năng ghi đè phiên bản hiện tại Now.
Hux

1
Đã thử sử dụng cái này nhưng có một vấn đề trên các chủ đề nên đã đi với một hiệu trưởng tương tự nhưng sử dụng AsyncLocalchứ không phảiThreadLocal
rrrr

@rrrr: bạn có thể giải thích vấn đề bạn gặp phải không?
Henk van Boeijen

@HenkvanBoeijen Chắc chắn, chúng tôi gặp vấn đề khi thời gian được đặt và sau đó chúng tôi đang chờ một phương thức không đồng bộ. 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 Tôi đã thêm một câu trả lời với những gì tôi đã kết thúc bằng cách sử dụng liên kết
rrrr

3

Tôi gặp vấn đề tương tự nhưng tìm thấy một dự án nghiên cứu từ Microsoft giải quyết vấn đề này.

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

Moles là một khung công tác nhẹ để kiểm tra sơ khai và đường vòng trong .NET dựa trên các đại biểu. Các nốt ruồi có thể được sử dụng để đi đường vòng bất kỳ phương thức .NET nào, kể cả các phương thức không ảo / tĩnh trong các loại kín

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

Mã mẫu đã được sửa đổi từ bản gốc.

Tôi đã thực hiện những gì khác đề xuất và trừu tượng hóa DateTime thành nhà cung cấp. Nó chỉ cảm thấy sai và tôi cảm thấy như nó là quá nhiều chỉ để thử nghiệm. Tôi sẽ thực hiện điều này vào dự án cá nhân của tôi tối nay.


2
BTW, điều này chỉ hoạt động với VS2010. Tôi đã rất buồn khi thấy rằng Moles hiện là Fakes cho VS2012 và nó chỉ có sẵn cho Premium và Ultimate Visual Studios. Không có Fakes cho VS 2012 Professional. Vì vậy, tôi không bao giờ phải thực sự sử dụng này. :(
Bobby Cannon

3

Một lưu ý đặc biệt khi chế nhạo DateTime.Nowvới TypeMock ...

Giá trị của DateTime.Nowphải được đặt vào một biến để điều này được chế giễu đúng. Ví dụ:

Điều này không hoạt động:

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

Tuy nhiên, điều này không:

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

2

Đối tượng giả.

Một DateTime giả trả về Bây giờ phù hợp với bài kiểm tra của bạn.


2

Tôi ngạc nhiên không ai đề xuất một trong những cách rõ ràng nhất để đi:

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

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

Sau đó, bạn có thể chỉ cần ghi đè phương thức này trong bài kiểm tra của bạn.

Tôi cũng giống như tiêm một TimeProviderlớp trong một số trường hợp, nhưng đối với những người khác, điều này là quá đủ. Có lẽ tôi thích TimeProviderphiên bản này nếu bạn cần sử dụng lại nó trong một số lớp.

EDIT: Đối với bất kỳ ai quan tâm, điều này được gọi là thêm "đường may" vào lớp của bạn, một điểm mà bạn có thể nối vào hành vi của nó để sửa đổi nó (cho mục đích thử nghiệm hoặc theo cách khác) mà không thực sự phải thay đổi mã trong lớp.


1

Thực hành tốt là, khi DateTimeProvider thực hiện IDis Dùng một lần.

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

Tiếp theo, bạn có thể tiêm DateTime giả cho các bài kiểm tra đơn vị

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

Xem ví dụ Ví dụ về DateTimeProvider


1

Một cách rõ ràng để làm điều này là tiêm VirtualTime. Nó cho phép bạn kiểm soát thời gian. Lần đầu tiên cài đặt VirtualTime

Install-Package VirtualTime

Điều đó cho phép, ví dụ, tạo thời gian di chuyển nhanh hơn 5 lần trên tất cả các cuộc gọi đến DateTime. Bây giờ hoặc UtcNow

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

Để làm cho thời gian di chuyển chậm hơn, ví dụ chậm hơn 5 lần

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

Để thời gian đứng yên

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

Di chuyển ngược thời gian chưa được thử nghiệm

Dưới đây là một bài kiểm tra mẫu:

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

Bạn có thể kiểm tra thêm các bài kiểm tra ở đây:

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

Phần mở rộng DateTime.Now.ToVirtualTime cung cấp cho bạn là một phiên bản của ITime mà bạn chuyển đến một phương thức / lớp phụ thuộc vào ITime. một số DateTime.Now.ToVirtualTime được thiết lập trong bộ chứa DI bạn chọn

Dưới đây là một ví dụ khác về tiêm thuốc vào lớp

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

Ngoài ra còn có một bài đọc tốt về mã kiểm tra phụ thuộc vào DateTime bởi Ayende tại đây ayende.com/blog/3408/deals-with-time-in-tests
Samuel

1

Chúng tôi đã sử dụng một đối tượng SystemTime tĩnh, nhưng gặp vấn đề khi chạy các bài kiểm tra đơn vị song song. Tôi đã cố gắng sử dụng giải pháp của Henk van Boeijen nhưng gặp vấn đề trên các luồng không đồng bộ được sinh ra, cuối cùng đã sử dụng AsyncLocal theo cách tương tự như dưới đây:

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

Được cung cấp từ https://gist.github.com/CraftyFella/42f459f7687b0b8b268fc311e6b4af08


1

Một câu hỏi cũ, nhưng vẫn còn hiệu lực.

Cách tiếp cận của tôi là tạo ra một giao diện và lớp mới để thực hiện System.DateTime.Nowcuộc gọi

public interface INow
{
    DateTime Execute();
}

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

Giao diện này có thể được đưa vào bất kỳ lớp nào cần để có được ngày và giờ hiện tại. Trong ví dụ này, tôi có một lớp thêm thời gian vào ngày và giờ hiện tại (một đơn vị có thể kiểm tra 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);
    }
}

Và các bài kiểm tra có thể được viết để đảm bảo rằng nó hoạt động chính xác. Tôi đang sử dụng NUnit và Moq nhưng mọi khung kiểm tra sẽ làm

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

Mô hình này sẽ làm việc cho bất kỳ chức năng mà phụ thuộc vào các yếu tố bên ngoài, giống như System.Random.Next(), System.DateTime.Now.UtcNow, System.Guid.NewGuid(), vv

Xem https://loadlrict.visualstudio.com/Stamina/_git/Stamina.Core để biết thêm ví dụ hoặc nhận gói https://www.nuget.org/packages/Stamina.Core nuget.


1

Bạn có thể thay đổi lớp mà bạn đang kiểm tra để sử dụng lớp Func<DateTime>sẽ được truyền qua các tham số của hàm tạo, vì vậy khi bạn tạo thể hiện của lớp trong mã thực, bạn có thể chuyển () => DateTime.UtcNowđếnFunc<DateTime> tham số và trong bài kiểm tra, bạn có thể vượt qua thời gian bạn muốn kiểm tra.

Ví dụ:

    [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
Tôi thích câu trả lời này. Từ những gì tôi đã thu thập được, đây có thể là điều gây chú ý trong thế giới C # nơi tập trung nhiều hơn vào các lớp học / đối tượng. Dù bằng cách nào, tôi thích điều này bởi vì nó thực sự đơn giản và rõ ràng với tôi những gì đang xảy ra.
giả hành

0

Tôi có cùng một vấn đề, nhưng tôi đã nghĩ rằng chúng ta không nên sử dụng các thứ datetime đã đặt trên cùng một lớp. bởi vì nó có thể dẫn đến việc sử dụng sai một ngày vì vậy tôi đã sử dụng nhà cung cấp như

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

Đối với các bài kiểm tra, tại dự án thử nghiệm đã tạo ra một người trợ giúp sẽ giải quyết mọi việc,

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

trên mã

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

và trong các bài kiểm tra

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

Nhưng cần phải nhớ một điều, đôi khi DateTime thực và DateTime của nhà cung cấp không hoạt động giống nhau

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

Tôi giả sử độ trễ sẽ là TimeSpan.FromMilliseconds tối đa (0,00002) . Nhưng hầu hết thời gian nó thậm chí còn ít hơn

Tìm mẫu tại MockSamples


0

Đây là anwer của tôi về câu hỏi này. Tôi kết hợp mẫu 'Bối cảnh xung quanh' với IDis Dùng. Vì vậy, bạn có thể sử dụng DateTimeProvider.C hiện tại trong mã chương trình bình thường của bạn và trong thử nghiệm, bạn ghi đè phạm vi bằng một câu lệnh sử dụng.

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

Dưới đây là cách sử dụng DateTimeProvider ở trên trong Bài kiểm tra đơn vị

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

Sử dụng ITimeProviderchúng tôi đã buộc phải đưa nó vào dự án chung được chia sẻ đặc biệt phải được tham chiếu từ phần còn lại của các dự án khác. Nhưng điều này làm phức tạp việc kiểm soát các phụ thuộc .

Chúng tôi đã tìm kiếm ITimeProvidertrong .NET framework. Chúng tôi đã tìm kiếm cho gói NuGet, và tìm thấy một mà không thể làm việc với DateTimeOffset.

Vì vậy, chúng tôi đã đưa ra giải pháp riêng của chúng tôi, chỉ phụ thuộc vào các loại thư viện chuẩn. Chúng tôi đang sử dụng một ví dụ về Func<DateTimeOffset>.

Cách sử dụng

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

Làm thế nào để đăng ký

Tự động điền

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

( Đối với các biên tập viên trong tương lai: nối các trường hợp của bạn ở đây ).

Cách kiểm tra đơn vị

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

Có thể ít chuyên nghiệp hơn nhưng giải pháp đơn giản hơn có thể tạo tham số DateTime tại phương thức của người tiêu dùng. Ví dụ: thay vì thực hiện phương thức như SampleMethod, hãy tạo SampleMethod1 bằng tham số. Việc kiểm tra SampleMethod1 dễ dàng hơn

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

        }

    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.