단위 테스트 : DateTime.Now


164

'현재 시간'이 DateTime과 다를 것으로 예상되는 단위 테스트가 있습니다. 지금 컴퓨터 시간을 변경하고 싶지 않습니다.

이를 달성하기위한 최선의 전략은 무엇입니까?


단위 테스트는 가짜 데이터를 사용하기 때문에 현재 시간에 오래된 값을 저장하고 datetime.now와 비교하십시오. 그렇지 않습니까?
Prashant Lakhlani

3
현재 DateTime의 추상화를 제공하는 것은 테스트 및 디버깅뿐만 아니라 프로덕션 코드에서도 유용합니다. 응용 프로그램 요구에 따라 다릅니다.
Pavel Hodek

1
DateTime을 얻기 위해 정적 클래스를 사용하지 마십시오
Daniel Little

VirtualTime을 사용하는 또 다른 간단한 방법
Samuel

작동하고 오버로드를 추가 할 수있는 한 가지 옵션은을 허용하는 메서드의 오버로드를 만드는 것입니다 DateTime. 이것은 당신이 테스트하는 것이 될 것이고, 기존의 메소드는 단순히 오버로드를 호출합니다 now.
Phil Cooper

답변:


218

최선의 전략이다 추상화에 현재 시간을 포장하고 소비자에 그 추상화를 주입 .


또는 시간 추상화를 앰비언트 컨텍스트 로 정의 할 수도 있습니다 .

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

이를 통해 다음과 같이 소비 할 수 있습니다.

var now = TimeProvider.Current.UtcNow;

단위 테스트에서는 TimeProvider.CurrentTest Double / Mock 개체로 대체 할 수 있습니다 . Moq를 사용한 예 :

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

그러나 정적 상태에서 유닛을 테스트 할 때는 항상 을 호출 하여 조명기분해해야합니다TimeProvider.ResetToDefault() .


7
주변 환경의 경우 해체하더라도 테스트를 어떻게 병렬로 실행합니까?
Ilya Chernomordik

2
@IlyaChernomordik ILogger 또는 기타 Cross-Cutting Concerns 를 주입 할 필요가 없으므로 시간 제공자를 주입하는 것이 여전히 가장 좋은 방법입니다.
Mark Seemann

5
@MikeK 내 대답의 첫 문장은 다음과 같습니다 . 가장 좋은 전략은 현재 시간을 추상화로 감싸고 그 추상화를 소비자에게 주입하는 것입니다. 이 답변의 다른 모든 것이 두 번째로 좋은 대안입니다.
Mark Seemann

3
안녕하세요. 멍청한 놈 이 솔루션으로 사람들이 DateTime.UtcNow에 액세스하는 것을 어떻게 방지합니까? TimeProvider를 사용하면 누군가가 놓치기 쉽습니다. 따라서 테스트에 버그가 있습니까?
Obbles

1
@Obbles 코드 검토 (또는 라이브 코드 검토의 한 형태 인 페어 프로그래밍). 코드베이스를 스캔하는 도구를 작성할 수 DateTime.UtcNow는 있지만 코드 검토는 좋은 생각입니다.
Mark Seemann 2:10에

58

이것들은 모두 좋은 답변입니다. 이것은 내가 다른 프로젝트에서 한 일입니다.

용법:

오늘의 실제 날짜 시간 가져 오기

var today = SystemTime.Now().Date;

DateTime.Now를 사용하는 대신 다음을 사용해야합니다 SystemTime.Now(). 어려운 변경은 아니지만이 솔루션이 모든 프로젝트에 적합하지는 않습니다.

시간 여행 (미래에 5 년 가야 함)

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

우리의 가짜를 "오늘"얻으십시오

var fakeToday = SystemTime.Now().Date;

날짜 재설정

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
고마워, 나는이 기능적인 aproach를 좋아한다. 오늘 (2 년 후) 여전히 수정 된 버전의 TimeProvider 클래스를 사용하고 있습니다 (허용 된 답변을 확인하십시오).
Pedro

7
@crabCRUSHERclamCOLI : ayende.com/blog/3408/dealing-with-time-in-tests에서 아이디어를 얻은 경우 , 다음은 링크에 좋은 일입니다.
Johann Gerell

3
이 개념은 Guid 생성을 조롱하는 데에도 효과적입니다 (public static Func <Guid> NewGuid = () => Guid.NewGuid ();
mdwhatcott

2
이 유용한 메소드를 추가 할 수도 있습니다. public static void ResetDateTime (DateTime dateTimeNow) {var timespanDiff = TimeSpan.FromTicks (DateTime.Now.Ticks-dateTimeNow.Ticks); 이제 = () => DateTime.Now-timespanDiff; }
Pavel Hodek

17
이것은 병렬로 실행되는 다른 장치 테스트에 영향을 줄 수 있습니다.
Amete Blessed

24

두더지 :

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

면책 조항-나는 두더지에서 일합니다


1
불행히도 nunit 및 resharper에서는 작동하지 않습니다.
odyth

Moles가 현재 지원되지 않는 것처럼 보이며 Fakes msdn.microsoft.com/en-us/library/… 로 대체되었습니다. MS가 너무 빨리 중단 할 것입니다
danio

17

이를위한 몇 가지 옵션이 있습니다.

  1. 모의 프레임 워크를 사용하고 DateTimeService를 사용하십시오 (작은 랩퍼 클래스를 구현하고 프로덕션 코드에 삽입하십시오). 래퍼 구현은 DateTime에 액세스하며 테스트에서 래퍼 클래스를 조롱 할 수 있습니다.

  2. Typemock Isolator를 사용하면 DateTime위조 할 수 있으며 테스트중인 코드를 변경할 필요가 없습니다.

  3. Moles를 사용하면 DateTime가짜 로 만들 수 있으며 프로덕션 코드를 변경할 필요가 없습니다.

몇 가지 예 :

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

아이솔레이터를 사용하여 직접 DateTime 가짜 :

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

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

면책 조항-나는 Typemock에서 일합니다


12

시스템에 가짜 어셈블리를 추가하십시오 (시스템 참조 => 가짜 어셈블리 추가를 마우스 오른쪽 단추로 클릭하십시오).

테스트 방법을 작성하십시오.

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

Vs Premium 또는 Ultimate에 액세스 할 수있는 경우 가장 쉽고 빠른 방법입니다.
Rugdr

7

@crabcrusherclamcollector 답변과 관련하여 EF 쿼리에서 해당 방법을 사용할 때 문제가 발생합니다 (System.NotSupportedException : LINQ 표현식 노드 유형 'Invoke'는 LINQ to Entities에서 지원되지 않음). 구현을 다음과 같이 수정했습니다.

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에서 Entity Framework를 의미한다고 생각합니까? SystemTime을 사용하는 객체는 어떻게 생겼습니까? 내 생각 엔 당신이 대신 당신이 당신의 엔티티에 정기적 DateTime 개체를 사용한다, 실제 개체에 SYSTEMTIME에 대한 참조를 가지고 있고 그것이 응용 프로그램에서 의미가 어디에 SYSTEMTIME 사용하여 설정
crabCRUSHERclamCOLLECTOR

1
예 linq 쿼리 및 EntityFramework6을 만들고 SystemTime에서 해당 쿼리 UtcNow를 참조 할 때 테스트했습니다. 설명 된대로 예외가있었습니다. 구현을 변경하면 잘 작동합니다.
marcinn

나는이 해결책을 좋아한다! 큰!
mirind4

7

온 의존하는 코드를 테스트하려면 System.DateTime의를system.dll 조롱해야합니다.

내가 알고있는 두 가지 프레임 워크가 있습니다. Microsoft 가짜작업복 .

Microsoft 가짜는 Visual Studio 2012 최후 통첩이 필요하며 compton에서 바로 작동합니다.

작업복은 오픈 소스이며 사용하기 매우 쉽습니다. NuGet을 사용하여 다운로드 할 수 있습니다.

다음은 모의를 보여줍니다 System.DateTime.

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

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

5

스레드 안전을 SystemClock사용 ThreadLocal<T>하면 나에게 효과적입니다.

ThreadLocal<T> .Net Framework v4.0 이상에서 사용할 수 있습니다.

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

사용 예 :

[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
+! 스레드 로컬은 병렬 테스트 실행 및 현재 테스트보다 우선하는 여러 테스트 문제를 피해야 Now합니다.
Hux

1
이것을 사용하려고했지만 쓰레드간에 문제가 있었기 때문에 비슷한 교장과 함께 갔지만 AsyncLocal오히려 사용했습니다ThreadLocal
rrrr

@ rrrr : 당신이 가진 문제를 설명 할 수 있습니까?
Henk van Boeijen

@HenkvanBoeijen 물론, 시간이 설정되었을 때 문제가 발생하여 비동기 메서드를 기다리고있었습니다. 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 링크
RRRR를

3

나는이 같은 문제에 부딪 쳤지 만이 문제를 해결하는 Microsoft의 연구 프로젝트를 찾았습니다.

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

Moles는 델리게이트를 기반으로하는 .NET의 테스트 스텁 및 우회 용 경량 프레임 워크입니다. 봉인 된 유형의 비가 상 / 정적 방법을 포함하여 .NET 방법을 우회하는 데 두더지를 사용할 수 있습니다.

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

샘플 코드는 원본에서 수정되었습니다.

나는 다른 것을 제안하고 DateTime을 공급자로 추상화했습니다. 방금 잘못 느꼈고 테스트하기에는 너무 많은 것 같았습니다. 오늘 저녁 내 개인 프로젝트에 이것을 구현할 것입니다.


2
BTW, 이것은 VS2010에서만 작동합니다. Moles가 이제 VS2012의 Fakes이며 Premium 및 Ultimate Visual Studio에서만 사용할 수 있음을 알게되었을 때 화가났습니다. VS 2012 Professional에는 가짜가 없습니다. 그래서 나는 이것을 실제로 사용해서는 안됩니다. :(
Bobby Cannon

3

TypeMock 으로 조롱 DateTime.Now하는 것에 대한 특별 참고 사항 ...

이 값을 DateTime.Now제대로 조롱하려면 변수 값을 변수에 배치해야합니다. 예를 들면 다음과 같습니다.

작동하지 않습니다.

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

그러나 이것은 다음을 수행합니다.

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

2

모의 객체.

테스트에 적합한 Now를 반환하는 모의 DateTime입니다.


2

아무도 가장 분명한 방법 중 하나를 제안하지 않은 것에 놀랐습니다.

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

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

그런 다음 테스트 에서이 방법을 간단히 재정의 할 수 있습니다.

나는 TimeProvider어떤 경우에는 수업을 주입하는 것과 비슷 하지만 다른 경우에는 이것으로 충분합니다. TimeProvider그래도 여러 클래스에서이를 재사용 해야하는 경우 버전을 선호합니다 .

편집 : 관심있는 사람을 위해 이것을 클래스에 "심"을 추가하는 것입니다. 클래스의 코드를 실제로 변경하지 않고도 테스트 목적 또는 기타 방식으로 클래스를 수정하는 동작에 연결할 수있는 지점입니다.


1

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

다음으로, 유닛 테스트를 위해 가짜 DateTime을 주입 할 수 있습니다

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

DateTimeProvider 예제 참조


1

이를 수행하는 한 가지 확실한 방법은 VirtualTime을 주입하는 것입니다. 시간을 제어 할 수 있습니다. 먼저 VirtualTime을 설치하십시오

Install-Package VirtualTime

예를 들어 DateTime에 대한 모든 호출에서 5 배 빠르게 이동하는 시간을 만들 수 있습니다.

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

시간 이동을 느리게하려면 (예 : 5 배 느리게)

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

시간을 멈추게하려면

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

뒤로 이동하는 것은 아직 테스트되지 않았습니다

다음은 샘플 테스트입니다.

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

여기에서 더 많은 테스트를 확인할 수 있습니다.

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

DateTime.Now.ToVirtualTime 확장이 제공하는 것은 ITime에 의존하는 메서드 / 클래스에 전달하는 ITime의 인스턴스입니다. 일부 DateTime.Now.ToVirtualTime은 선택한 DI 컨테이너에 설정됩니다.

다음은 클래스 위탁자에게 주입하는 또 다른 예입니다.

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의 DateTime에 의존하는 테스트 코드에 대한 좋은 기사도 있습니다. 여기 ayende.com/blog/3408/dealing-with-in-in-tests
Samuel

1

정적 SystemTime 객체를 사용했지만 병렬 단위 테스트를 실행하는 데 문제가 발생했습니다. Henk van Boeijen의 솔루션을 사용하려고 시도했지만 생성 된 비동기 스레드에서 문제가 발생했으며 다음과 유사한 방식으로 AsyncLocal을 사용했습니다.

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 에서 공급


1

오래된 질문이지만 여전히 유효합니다.

내 접근 방식은 새로운 인터페이스와 클래스를 만들어 System.DateTime.Now호출 을 래핑하는 것입니다.

public interface INow
{
    DateTime Execute();
}

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

이 인터페이스는 현재 날짜와 시간을 가져와야하는 모든 클래스에 삽입 할 수 있습니다. 이 예제에는 현재 날짜 및 시간에 시간 범위를 추가하는 클래스가 있습니다 (단위 테스트 가능한 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);
    }
}

테스트가 올바르게 작동하는지 확인할 수 있습니다. NUnit과 Moq를 사용하고 있지만 모든 테스트 프레임 워크는

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

이 패턴은 같은 외부 요인에 따라 어떤 기능 작동 System.Random.Next(), System.DateTime.Now.UtcNow, System.Guid.NewGuid()

추가 예제는 https://loadlimited.visualstudio.com/Stamina/_git/Stamina.Core 를 참조 하거나 https://www.nuget.org/packages/Stamina.Core 너겟 패키지를 참조하십시오 .


1

당신은 당신이 사용하는 테스트 클래스를 변경할 수 있습니다 Func<DateTime>당신이 진짜 코드에서 클래스의 인스턴스를 만들 때, 당신이 통과 할 수있다, 그것의 생성자 매개 변수를 통해 전달 될 것이다을 () => DateTime.UtcNow받는Func<DateTime> 매개 변수, 시험에, 당신은 시간을 통과 할 수 있습니다 시험하고 싶다.

예를 들면 다음과 같습니다.

    [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
나는이 답변을 좋아한다. 내가 수집 한 것에서 이것은 클래스 / 객체에 더 중점을 둔 C # 세계에서 눈을 뜨는 것일 수 있습니다. 어느 쪽이든, 나는 이것이 실제로 간단하고 명확하기 때문에 이것을 선호합니다.
pseudoramble

0

나는 같은 문제가 있었지만 같은 클래스에서 설정된 날짜 시간을 사용해서는 안된다고 생각했습니다. 하루 남용으로 이어질 수 있기 때문입니다. 그래서 나는 공급자를 사용했다

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

테스트를 위해 테스트 프로젝트에서 정해진 일을 처리하는 도우미를 만들었습니다.

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

코드에서

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

그리고 시험에

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

그러나 한 가지만 기억해야합니다. 때로는 실제 DateTime과 공급자의 DateTime이 동일하게 작동하지 않습니다.

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

지연이 최대 TimeSpan.FromMilliseconds (0.00002) 가 될 것이라고 가정했습니다 . 그러나 대부분의 경우 훨씬 적습니다.

MockSamples 에서 샘플 찾기


0

여기이 질문에 대한 답이 있습니다. 'Ambient Context'패턴과 IDisposable을 결합합니다. 따라서 일반적인 프로그램 코드에서 DateTimeProvider.Current를 사용할 수 있으며 테스트에서는 using 문으로 범위를 재정의합니다.

using System;
using System.Collections.Immutable;


namespace ambientcontext {

public abstract class DateTimeProvider : IDisposable
{
    private static ImmutableStack<DateTimeProvider> stack = ImmutableStack<DateTimeProvider>.Empty.Push(new DefaultDateTimeProvider());

    protected DateTimeProvider()
    {
        if (this.GetType() != typeof(DefaultDateTimeProvider))
            stack = stack.Push(this);
    }

    public static DateTimeProvider Current => stack.Peek();
    public abstract DateTime Today { get; }
    public abstract DateTime Now {get; }

    public void Dispose()
    {
        if (this.GetType() != typeof(DefaultDateTimeProvider))
            stack = stack.Pop();
    }

    // Not visible Default Implementation 
    private class DefaultDateTimeProvider : DateTimeProvider {
        public override DateTime Today => DateTime.Today; 
        public override DateTime Now => DateTime.Now; 
    }
}
}

위의 DateTimeProvider를 단위 테스트에서 사용하는 방법은 다음과 같습니다.

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

ITimeProvider우리는 그것을 사용하여 다른 프로젝트의 다른 프로젝트에서 참조 해야하는 공유 프로젝트를 공유 했습니다. 그러나 이것은 의존성 통제를 복잡하게 만들었다 .

ITimeProvider.NET 프레임 워크 에서을 검색했습니다 . 우리는 NuGet 패키지를 검색하고, 발견 작동하지 않을 수 있습니다 DateTimeOffset.

따라서 표준 라이브러리 유형에만 의존하는 자체 솔루션을 개발했습니다. 의 인스턴스를 사용하고 Func<DateTimeOffset>있습니다.

사용하는 방법

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

등록 방법

오토 팩

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

( 향후 편집자 : 사례를 여기에 추가하십시오 ).

단위 테스트 방법

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

덜 전문적이지만 간단한 솔루션은 소비자 방법에서 DateTime 매개 변수를 만들 수 있습니다. 예를 들어 SampleMethod와 같은 make 메서드 대신 SampleMethod1을 매개 변수로 만듭니다 .SampleMethod1 테스트가 더 쉽습니다.

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

        }

    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.