Какой хороший способ перезаписать DateTime.Now во время тестирования? - PullRequest
107 голосов
/ 04 сентября 2008

У меня есть некоторый (C #) код, который опирается на сегодняшнюю дату, чтобы правильно вычислять вещи в будущем. Если я использую сегодняшнюю дату в тестировании, я должен повторить расчет в тесте, который не кажется правильным. Каков наилучший способ установить дату на известное значение в тесте, чтобы я мог проверить, что результат является известным значением?

Ответы [ 11 ]

146 голосов
/ 04 сентября 2008

Я предпочитаю, чтобы классы, использующие время, фактически зависели от интерфейса, такого как

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 и включение его в ваши расчеты не просто кажется неправильным - это лишает вас возможности проверять определенное время, например, если вы обнаружите ошибку, которая происходит только около полуночной границы, или по вторникам. Использование текущего времени не позволит вам протестировать эти сценарии. Или, по крайней мере, не тогда, когда вы хотите.

55 голосов
/ 04 сентября 2008

Ayende Rahien использует статический метод, который довольно прост ...

public static class SystemTime
{
    public static Func<DateTime> Now = () => DateTime.Now;
}
17 голосов
/ 04 сентября 2008

Я думаю, что создание отдельного класса часов для чего-то простого, например, получение текущей даты, немного излишне.

Вы можете передать сегодняшнюю дату в качестве параметра, чтобы вы могли ввести другую дату в тесте. Это дает дополнительное преимущество, делая ваш код более гибким.

16 голосов
/ 03 января 2014

Использование Microsoft Fakes для создания прокладки - это действительно простой способ сделать это. Предположим, у меня был следующий класс:

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

}

В Visual Studio 2012 вы можете добавить сборку Fakes в ваш тестовый проект, щелкнув правой кнопкой мыши сборку, для которой вы хотите создать Fakes / Shims, и выбрав «Добавить сборку Fakes»

Adding Fakes Assembly

Наконец, вот как будет выглядеть тестовый класс:

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

        }
    }
}
}
10 голосов
/ 02 февраля 2010

Еще один, использующий Microsoft Moles ( Изолирующая среда для .NET ).

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

Родинок позволяет заменить любой .NET метод с делегатом. Родинки поддерживает статические или не виртуальные методы. Кроты полагается на профилировщик от Pex.

10 голосов
/ 09 сентября 2008

Ключ к успешному юнит-тестированию - развязка . Вы должны отделить ваш интересный код от его внешних зависимостей, чтобы его можно было тестировать изолированно. (К счастью, Test-Driven Development создает разъединенный код.)

В этом случае вашим внешним является текущий DateTime.

Мой совет здесь состоит в том, чтобы извлечь логику, имеющую дело с DateTime, в новый метод или класс или что-либо еще, что имеет смысл в вашем случае, и передать DateTime. Теперь ваш модульный тест может пройти произвольный DateTime, чтобы предсказуемые результаты.

4 голосов
/ 02 февраля 2010

Я бы предложил использовать шаблон IDisposable:

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

Подробно описано здесь: http://www.lesnikowski.com/blog/index.php/testing-datetime-now/

3 голосов
/ 18 ноября 2014

Простой ответ: ditch System.DateTime :) Вместо этого используйте NodaTime и его библиотеку тестирования: NodaTime.Testing .

Дальнейшее чтение:

2 голосов
/ 04 сентября 2008

Вы можете добавить класс (лучше: метод / делегат ), который вы используете для DateTime.Now в тестируемом классе. DateTime.Now должно быть значением по умолчанию и устанавливать его при тестировании только на фиктивный метод, который возвращает постоянное значение.

РЕДАКТИРОВАТЬ: То, что сказал Блэр Конрад (у него есть код для просмотра). За исключением того, что я предпочитаю для этого делегатов, поскольку они не загромождают вашу иерархию классов такими вещами, как IClock ...

1 голос
/ 27 июня 2018

Я сталкивался с такой ситуацией так часто, что создал простой nuget, который через интерфейс * выставляет свойство Now .

public interface IDateTimeTools
{
    DateTime Now { get; }
}

Реализация, конечно, очень проста

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

Так что после добавления nuget в мой проект я могу использовать его в модульных тестах

enter image description here

Вы можете установить модуль прямо из GUI Nuget Package Manager или с помощью команды:

Install-Package -Id DateTimePT -ProjectName Project

А код для Nuget здесь .

Пример использования с Autofac можно найти здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...