단위 테스트 : DateTime.Now
'현재 시간'이 DateTime과 다를 것으로 예상되는 단위 테스트가 있습니다. 지금 컴퓨터 시간을 변경하고 싶지 않습니다.
이를 달성하기위한 최선의 전략은 무엇입니까?
최선의 전략이다 추상화에 현재 시간을 포장하고 소비자에 그 추상화를 주입 .
또는 시간 추상화를 앰비언트 컨텍스트 로 정의 할 수도 있습니다 .
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.Current
Test 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()
.
이것들은 모두 좋은 답변입니다. 이것은 내가 다른 프로젝트에서 한 일입니다.
용법:
오늘의 실제 날짜 시간 가져 오기
var today = SystemTime.Now().Date;
DateTime.Now를 사용하는 대신 다음을 사용해야합니다 SystemTime.Now()
. 어려운 변경은 아니지만이 솔루션이 모든 프로젝트에 적합하지는 않습니다.
시간 여행 (미래에 5 년 가야 함)
SystemTime.SetDateTime(today.AddYears(5));
우리의 가짜를 "오늘"얻으십시오 ( '오늘'에서 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;
}
}
두더지 :
[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
}
면책 조항-나는 두더지에서 일합니다
이를위한 몇 가지 옵션이 있습니다.
모의 프레임 워크를 사용하고 DateTimeService를 사용하십시오 (작은 랩퍼 클래스를 구현하고 프로덕션 코드에 삽입하십시오). 래퍼 구현은 DateTime에 액세스하며 테스트에서 래퍼 클래스를 조롱 할 수 있습니다.
Typemock Isolator를 사용하면 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에서 일합니다
시스템에 가짜 어셈블리를 추가하십시오 (시스템 참조 => 가짜 어셈블리 추가를 마우스 오른쪽 단추로 클릭하십시오).
테스트 방법을 작성하십시오.
using (ShimsContext.Create())
{
System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10);
MethodThatUsesDateTimeNow();
}
@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;
}
}
}
스레드 안전을 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();
}
온 의존하는 코드를 테스트하기 위해 System.DateTime
의를 system.dll
조롱해야합니다.
내가 알고있는 두 가지 프레임 워크가 있습니다. 마이크로 소프트 가짜 및 작업복 .
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);
});
나는이 같은 문제에 부딪 쳤지 만이 문제를 해결하는 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을 공급자로 추상화했습니다. 방금 잘못 느꼈고 테스트하기에는 너무 많은 것 같았습니다. 오늘 저녁 내 개인 프로젝트에 이것을 구현할 것입니다.
모의 객체.
테스트에 적합한 Now를 반환하는 모의 DateTime입니다.
아무도 가장 분명한 방법 중 하나를 제안하지 않은 것에 놀랐습니다.
public class TimeDependentClass
{
public void TimeDependentMethod(DateTime someTime)
{
if (GetCurrentTime() > someTime) DoSomething();
}
protected virtual DateTime GetCurrentTime()
{
return DateTime.Now; // or UtcNow
}
}
그런 다음 테스트 에서이 방법을 간단히 재정의 할 수 있습니다.
나는 TimeProvider
어떤 경우에는 수업을 주입하는 것과 비슷 하지만 다른 경우에는 이것으로 충분합니다. TimeProvider
그래도 여러 클래스에서이를 재사용 해야하는 경우 버전을 선호합니다 .
편집 : 관심있는 사람을 위해 이것을 클래스에 "심"을 추가하는 것입니다. 클래스의 코드를 실제로 변경하지 않고도 테스트 목적 또는 기타 방식으로 클래스를 수정하는 동작에 연결할 수있는 지점입니다.
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))
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 예제 예 참조
이를 수행하는 한 가지 확실한 방법은 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));
}
여기에서 더 많은 테스트를 확인할 수 있습니다.
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);
}
나는 같은 문제가 있었지만 같은 클래스에서 설정된 날짜 시간을 사용해서는 안된다고 생각했습니다. 하루 남용으로 이어질 수 있기 때문입니다. 그래서 나는 공급자를 사용했다
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 에서 샘플 찾기
언급되지 않은 대체 옵션은 현재 시간을 종속 메소드에 삽입하는 것입니다.
public class DateTimeNowDependencyClass
{
...
public void ImplicitTimeDependencyMethod(Obj arg0)
{
this.TimeDependencyMethod(DateTime.Now, arg0);
}
internal void TimeDependencyMethod(DateTime now, Obj arg1)
{
...
}
...
}
내부 변형은 병행 여부에 관계없이 단위 테스트에 개방적입니다.
이것은 ImplicitTimeDependencyMethod
"너무 깨지기 쉽다"( http://junit.sourceforge.net/doc/faq/faq.htm#best_3 참조 ) 원칙을 기반으로하기 때문에 단위 테스트 범위에 포함될 필요는 없습니다. 어쨌든 통합 테스트에서 다루어 져야합니다.
수업의 목적에 따라이 두 방법을 모두 공개하는 것이 바람직 할 수 있습니다.
여기이 질문에 대한 답이 있습니다. '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;
}
}
}
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);
}
덜 전문적이지만 간단한 솔루션은 소비자 방법에서 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)
{
}
}
정적 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 에서 공급
참고 URL : https://stackoverflow.com/questions/2425721/unit-testing-datetime-now
'Programing' 카테고리의 다른 글
C / C ++에서 NULL 포인터 확인 (0) | 2020.06.18 |
---|---|
PDO는 마지막으로 삽입 된 ID를 얻습니다 (0) | 2020.06.18 |
postgresql에서 함수, 프로 시저를 표시하고 소스 코드를 트리거하는 방법은 무엇입니까? (0) | 2020.06.18 |
문자열을 System.IO.Stream으로 변환 (0) | 2020.06.18 |
EntityManager에 대한 지속성 제공자가 없습니다. (0) | 2020.06.18 |