Programming

테스트하는 동안 DateTime.Now를 덮어 쓰는 좋은 방법은 무엇입니까?

procodes 2020. 8. 3. 09:02
반응형

테스트하는 동안 DateTime.Now를 덮어 쓰는 좋은 방법은 무엇입니까?


미래의 일을 올바르게 계산하기 위해 오늘 날짜에 의존하는 일부 (C #) 코드가 있습니다. 테스트에서 오늘 날짜를 사용하는 경우 테스트에서 계산을 반복해야하는데 옳지 않습니다. 결과가 알려진 값인지 테스트 할 수 있도록 테스트 내에서 날짜를 알려진 값으로 설정하는 가장 좋은 방법은 무엇입니까?


내 선호는 시간을 사용하는 클래스가 실제로 인터페이스와 같은 인터페이스에 의존하는 것입니다.

interface IClock
{
    DateTime Now { get; } 
}

구체적인 구현

class SystemClock: IClock
{
     DateTime Now { get { return DateTime.Now; } }
}

그런 다음 원하는 경우 테스트를 위해 원하는 다른 종류의 시계를 제공 할 수 있습니다.

class StaticClock: IClock
{
     DateTime Now { get { return new DateTime(2008, 09, 3, 9, 6, 13); } }
}

시계에 의존하는 클래스에 시계를 제공하는 데 약간의 오버 헤드가있을 수 있지만, 많은 수의 의존성 주입 솔루션 (Inversion of Control 컨테이너, 일반 오래된 생성자 / 세터 주입 또는 정적 게이트웨이 패턴 사용)에 의해 처리 될 수 있습니다 ).

원하는 시간을 제공하는 객체 또는 메소드를 전달하는 다른 메커니즘도 작동하지만 핵심은 시스템 시계를 재설정하지 않는 것이 다른 수준에서 고통을 초래할 수 있기 때문이라고 생각합니다.

또한 DateTime.Now계산에 사용 하고 포함시키는 것이 옳지 않은 것은 아닙니다. 예를 들어 자정 경계 근처 나 화요일에 발생하는 버그를 발견 한 경우와 같이 특정 시간을 테스트 할 수있는 능력을 빼앗습니다. 현재 시간을 사용하면 해당 시나리오를 테스트 할 수 없습니다. 또는 적어도 당신이 원할 때마다 아닙니다.


Ayende Rahien 은 다소 간단한 정적 방법을 사용 합니다.

public static class SystemTime
{
    public static Func<DateTime> Now = () => DateTime.Now;
}

나는 현재 날짜를 얻는 것과 같은 간단한 것에 대해 별도의 시계 클래스를 만드는 것이 약간 과잉이라고 생각합니다.

오늘 날짜를 매개 변수로 전달하여 테스트에서 다른 날짜를 입력 할 수 있습니다. 이렇게하면 코드를보다 유연하게 만들 수 있다는 이점이 있습니다.


Microsoft Fakes를 사용하여 shim을 만드는 것이 정말 쉬운 방법입니다. 내가 다음 수업을 받았다고 가정 해보십시오.

public class MyClass
{
    public string WhatsTheTime()
    {
        return DateTime.Now.ToString();
    }

}

Visual Studio 2012에서는 가짜 / 심을 만들 어셈블리를 마우스 오른쪽 단추로 클릭하고 "가짜 어셈블리 추가"를 선택하여 테스트 프로젝트에 가짜 어셈블리를 추가 할 수 있습니다.

가짜 조립품 추가

마지막으로 테스트 클래스는 다음과 같습니다.

using System;
using ConsoleApplication11;
using Microsoft.QualityTools.Testing.Fakes;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DateTimeTest
{
[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestWhatsTheTime()
    {

        using(ShimsContext.Create()){

            //Arrange
            System.Fakes.ShimDateTime.NowGet =
            () =>
            { return new DateTime(2010, 1, 1); };

            var myClass = new MyClass();

            //Act
            var timeString = myClass.WhatsTheTime();

            //Assert
            Assert.AreEqual("1/1/2010 12:00:00 AM",timeString);

        }
    }
}
}

성공적인 단위 테스트의 핵심은 디커플링 입니다. 흥미로운 코드를 외부 종속성과 분리해야하므로 별도로 테스트 할 수 있습니다. 운 좋게도 테스트 주도 개발은 분리 된 코드를 생성합니다.

이 경우 외부는 현재 날짜 / 시간입니다.

내 조언은 DateTime을 처리하는 논리를 새로운 메소드 또는 클래스 또는 귀하의 경우에 의미가있는 것으로 추출하고 DateTime을 전달하는 것입니다. 이제 단위 테스트는 임의의 DateTime을 전달하여 예측 가능한 결과를 생성 할 수 있습니다.


Another one using Microsoft Moles (Isolation framework for .NET).

MDateTime.NowGet = () => new DateTime(2000, 1, 1);

Moles allows to replace any .NET method with a delegate. Moles supports static or non-virtual methods. Moles relies on the profiler from Pex.


I'd suggest using IDisposable pattern:

[Test] 
public void CreateName_AddsCurrentTimeAtEnd() 
{
    using (Clock.NowIs(new DateTime(2010, 12, 31, 23, 59, 00)))
    {
        string name = new ReportNameService().CreateName(...);
        Assert.AreEqual("name 2010-12-31 23:59:00", name);
    } 
}

In detail described here: http://www.lesnikowski.com/blog/index.php/testing-datetime-now/


Simple answer: ditch System.DateTime :) Instead, use NodaTime and it's testing library: NodaTime.Testing.

Further reading:


You could inject the class (better: method/delegate) you use for DateTime.Now in the class being tested. Have DateTime.Now be a default value and only set it in testing to a dummy method that returns a constant value.

EDIT: What Blair Conrad said (he has some code to look at). Except, I tend to prefer delegates for this, as they don't clutter up your class hierarchy with stuff like IClock...


I faced this situation so often, that I created simple nuget which exposes Now property through interface.

public interface IDateTimeTools
{
    DateTime Now { get; }
}

Implementation is of course very straightforward

public class DateTimeTools : IDateTimeTools
{
    public DateTime Now => DateTime.Now;
}

So after adding nuget to my project I can use it in the unit tests

여기에 이미지 설명을 입력하십시오

You can install module right from the GUI Nuget Package Manager or by using the command:

Install-Package -Id DateTimePT -ProjectName Project

And the code for the Nuget is here.

The example of usage with the Autofac can be found here.


Have you considered using conditional compilation to control what happens during debug/deployment?

e.g.

DateTime date;
#if DEBUG
  date = new DateTime(2008, 09, 04);
#else
  date = DateTime.Now;
#endif

Failing that, you want to expose the property so you can manipulate it, this is all part of the challenge of writing testable code, which is something I am currently wrestling myself :D

Edit

A big part of me would preference Blair's approach. This allows you to "hot plug" parts of the code to aid in testing. It all follows the design principle encapsulate what varies test code is no different to production code, its just no one ever sees it externally.

이 예제에서는 작성 및 인터페이스가 많은 작업처럼 보일 수 있습니다 (그래서 조건부 컴파일을 선택했습니다).

참고 URL : https://stackoverflow.com/questions/43711/whats-a-good-way-to-overwrite-datetime-now-during-testing

반응형