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


116

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

답변:


157

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

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계산에 사용 하고 포함하는 것은 옳다고 생각하지 않습니다. 예를 들어 자정 근처 또는 화요일에만 발생하는 버그를 발견 한 경우 특정 시간을 테스트 할 수있는 능력을 빼앗 깁니다. 현재 시간을 사용하면 이러한 시나리오를 테스트 할 수 없습니다. 아니면 적어도 원할 때마다.


2
우리는 실제로 이것을 xUnit.net 확장 중 하나로 공식화했습니다. DateTime 대신 정적으로 사용하는 Clock 클래스가 있으며 특정 날짜를 포함하여 시계를 "고정"및 "해동"할 수 있습니다. is.gd/3xdsis.gd/3xdu
Brad Wilson

2
또한 시스템 클록 방법을 대체하려는 경우 (예 : 광범위하게 흩어져있는 시간대에 지점이있는 기업에서 글로벌 클록을 사용할 때 발생합니다.)이 방법을 사용하면 비즈니스 수준에서 "지금"의 의미.
Mike Burton

1
이 방법은 IClock 인스턴스에 도달하기 위해 Dependency Injection Framework를 사용하는 것과 함께 저에게 정말 잘 작동합니다.
Wilka

8
좋은 대답입니다. 거의 모든 경우 UtcNow에 코드의 문제 (예 : 비즈니스 로직, UI 등)에 따라 적절하게 조정해야한다는 점을 추가하고 싶었습니다 . 시간대 전반에 걸친 DateTime 조작은 지뢰밭이지만 가장 좋은 첫 걸음은 항상 다음으로 시작하는 것입니다. UTC 시간.
Adam Ralph

@BradWilson 그 링크는 이제 끊어졌습니다. WayBack에서도 가져올 수 없습니다.

55

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

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

1
스텁 / 모의 포인트를 공용 전역 변수 (클래스 정적 변수)로 만드는 것은 위험 해 보입니다. 테스트 대상 시스템으로 범위를 지정하는 것이 더 낫지 않습니까?
Aaron

1
그것은 스타일의 문제입니다. 이것은 단위 테스트 변경 가능한 시스템 시간을 얻기 위해 할 수 있는 최소한의 일입니다.
Anthony Mastrean

3
IMHO, 전역 정적 싱글 톤 대신 인터페이스를 사용하는 것이 좋습니다. 다음 시나리오를 고려하십시오. 테스트 실행기는 효율적이고 가능한 한 많은 테스트를 병렬로 실행합니다. 하나의 테스트는 주어진 시간을 X로, 다른 하나는 Y로 변경됩니다. 이제 충돌이 발생하고이 두 테스트는 실패를 토글합니다. 인터페이스를 사용하는 경우 각 테스트는 필요에 따라 인터페이스를 조롱하고 각 테스트는 이제 다른 테스트와 격리됩니다. HTH.
ShloEmi 2015

17

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

오늘 날짜를 매개 변수로 전달하여 테스트에 다른 날짜를 입력 할 수 있습니다. 이것은 코드를 더 유연하게 만드는 추가적인 이점이 있습니다.


나는 당신과 블레어의 대답을 모두 +1했습니다. 둘 다 반대하지만. 두 가지 접근 방식이 모두 유효하다고 생각합니다. 귀하의 접근 방식은 아마도 Unity와 같은 것을 사용하지 않는 프로젝트를 사용할 것입니다.
RichardOD

1
예, "지금"에 대한 매개 변수를 추가 할 수 있습니다. 그러나 어떤 경우에는 일반적으로 노출하지 않으려는 매개 변수를 노출해야합니다. 예를 들어, 날짜와 현재 사이의 날짜를 계산하는 방법이 있다고 가정 해 봅시다. 그러면 결과를 조작 할 수 있기 때문에 "now"를 매개 변수로 노출하지 않으려 고합니다. 자신의 코드 인 경우 "지금"을 매개 변수로 추가하십시오.하지만 팀에서 작업하는 경우 다른 개발자가 귀하의 코드를 어떤 용도로 사용할지 전혀 알 수 없으므로 "지금"이 코드의 중요한 부분 인 경우 다음을 수행해야합니다. 조작 또는 오용으로부터 보호하십시오.
Stitch10925

17

Microsoft Fakes를 사용하여 shim을 만드는 것은 정말 쉬운 방법입니다. 다음 클래스가 있다고 가정합니다.

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

}

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

가짜 어셈블리 추가

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

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

        }
    }
}
}

1
이것이 바로 제가 찾던 것입니다. 감사! BTW, VS 2013에서도 동일하게 작동합니다.
Douglas Ludlow

또는 요즘 VS 2015 Enterprise 에디션. 그러한 모범 사례에 대한 수치입니다.
RJB

12

성공적인 단위 테스트의 핵심은 디커플링 입니다. 흥미로운 코드를 외부 종속성에서 분리해야 격리 된 상태에서 테스트 할 수 있습니다. (다행히도 Test-Driven Development는 분리 된 코드를 생성합니다.)

이 경우 외부는 현재 DateTime입니다.

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


10

Microsoft Moles ( .NET 용 격리 프레임 워크)를 사용하는 또 다른 하나 입니다.

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

두더지는 모든 .NET 메서드를 대리자로 바꿀 수 있습니다. 두더지는 정적 또는 비가 상 방법을 지원합니다. 두더지는 Pex의 프로파일 러에 의존합니다.


이것은 아름답지만 Visual Studio 2010이 필요합니다! :-(
Pandincus 2010 년

VS 2008에서도 잘 작동합니다. 하지만 MSTest에서 가장 잘 작동합니다. NUnit을 사용할 수 있지만 특별한 테스트 러너로 테스트를 실행해야한다고 생각합니다.
Torbjørn

가능하면 Moles (일명 Microsoft Fakes)를 사용하지 않을 것입니다. 이상적으로는 종속성 주입을 통해 아직 테스트 할 수없는 레거시 코드에만 사용해야합니다.
brianpeiris 2013 년

1
@brianpeiris, Microsoft Fakes 사용의 단점은 무엇입니까?
Ray Cheng

항상 제 3 자 콘텐츠를 DI 할 수는 없으므로 코드가 단위 테스트를 위해 인스턴스화하지 않으려는 타사 API를 호출하는 경우 Fakes는 단위 테스트에 완벽하게 허용됩니다. 본인은 자신의 코드에 페이크 / 두더지를 사용하지 않기로 동의하지만 다른 목적으로는 완벽하게 허용됩니다.
ChrisCW



2

테스트중인 클래스에서 사용 하는 클래스 (더 나은 : method / delegate )를 삽입 할 수 있습니다 DateTime.Now. 한 DateTime.Now디폴트 값은 더미 방법을 반환하는 상수 값 테스트에서 설정 될 수있다.

편집 : 블레어 콘래드가 말한 것 (그는 볼 코드가 있음). 제외하고, 나는 이것을 위해 대리자를 선호하는 경향이 있습니다. 그들은 당신의 클래스 계층을 다음과 같은 것들로 어지럽히 지 않기 때문입니다 IClock.


1

이 상황에 너무 자주 직면 하여 인터페이스를 통해 Now 속성을 노출하는 간단한 너겟을 만들었습니다 .

public interface IDateTimeTools
{
    DateTime Now { get; }
}

물론 구현은 매우 간단합니다.

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

따라서 프로젝트에 너겟을 추가 한 후 단위 테스트에서 사용할 수 있습니다.

여기에 이미지 설명 입력

GUI Nuget Package Manager에서 직접 또는 다음 명령을 사용하여 모듈을 설치할 수 있습니다.

Install-Package -Id DateTimePT -ProjectName Project

그리고 Nuget의 코드는 여기에 있습니다 .

Autofac을 사용한 사용 예는 여기 에서 찾을 수 있습니다 .


-11

조건부 컴파일을 사용하여 디버그 / 배포 중에 발생하는 작업을 제어하는 ​​것을 고려해 보셨습니까?

예 :

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

실패하면 속성을 노출하여 조작 할 수 있습니다. 이것은 테스트 가능한 코드 를 작성해야하는 도전의 일부입니다 .

편집하다

저의 큰 부분은 블레어의 접근 방식을 선호 할 것 입니다. 이를 통해 테스트에 도움이되도록 코드의 일부를 "핫 플러그"할 수 있습니다. 모든 설계 원리는 다음 없습니다 다릅니다 어떤 캡슐 은 그냥 아무도 외부에서 그것을보고, 테스트 코드가 생산 코드에 차이가 없습니다.

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


와우 당신은이 답변에 심하게 맞았습니다. 이것은 내가 비록 내가 대체 방법을 호출 한 곳에서, 해 봤는데 무엇 DateTimeNowToday
데이브 Cousineau
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.