테스트 중에 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/3xds 및 is.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

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

편집하다

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

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


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