Код модульного тестирования, который выполняет обработку даты на основе сегодняшней даты - PullRequest
15 голосов
/ 19 февраля 2009

Когда код обрабатывает даты на основе текущей даты, тестирование должно охватывать крайние случаи, такие как високосные годы, а также более частые границы месяца и года.

В нашем коде мы всегда получаем текущую дату в наших классах, используя DateTime.Now (в нашем случае - .NET).

Как вы можете протестировать такой код?

Это где Внедрение зависимостей становится очень полезным?

Редактировать

Это немного в стороне, но, очевидно, следующая версия Typemock позволит подделать DateTime.Now

https://blog.typemock.com/2009/05/mockingfaking-datetimenow-in-unit-tests.html

Ответы [ 9 ]

15 голосов
/ 19 февраля 2009

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

Это зависимость и недетерминированная зависимость. Вы должны разделить ответственность кода немного больше.

До:

  • Существует некоторый код, который использует текущую дату и время для выполнения X.

После того, как:

  • Должен быть какой-то код, который отвечает за получение текущей даты и времени.
  • Должен быть какой-то код, который использует дату и время для X.

Эти два набора кода не должны зависеть друг от друга.

Этот шаблон разделения зависимостей работает и в других случаях (база данных, файловая система и т. Д.).

12 голосов
/ 19 февраля 2009

Использование DI для введения «текущей даты и времени», безусловно, является излишним. Я бы предпочел рефакторинг кода для работы в произвольную дату. То есть я бы изменил function calculateRevenue() на function calculateRevenueOn(datetime date). Это гораздо проще протестировать и использовать в случае, если вам нужны вычисления для некоторых дат, отличных от текущих.

10 голосов
/ 19 февраля 2009

Я использовал очень прагматичный подход, рассмотренный Ореном Эйни (он же Айенде Райен) в своих блогах Работа со временем в тестах .

Существует такой статический класс:

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

твой код становится:

Entity.LastChange = SystemTime.Now();

И ваш тест станет:

SystemTime.Now = () => new DateTime(2000,1,1);
repository.ResetFailures(failedMsgs); 
SystemTime.Now = () => new DateTime(2000,1,2);
var msgs = repository.GetAllReadyMessages(); 
Assert.AreEqual(2, msgs.Length);
9 голосов
/ 19 февраля 2009

Всегда помещайте основную логику обработки даты (которую, по моему опыту, обычно легко выделить для этой цели) в отдельные методы, которые принимают дату в качестве параметра.

Затем в своем производственном коде вы можете вызывать их и давать им текущую дату в качестве параметра, и все же тестировать крайние значения для своего сердца в модульных тестах.

Кстати, я обнаружил, что крайне важно очень интенсивно тестировать логику дат для крайних случаев. Он устраняет (возможно, катастрофические, ссылка Zune) ошибки, которые вы никогда бы не нашли иначе. Помимо упомянутых вами, переход на летнее время также может быть проблематичным.

3 голосов
/ 19 февраля 2009

Вы уже дали ответ. Вы можете написать небольшой класс-оболочку для функций datetime, которые затем можно внедрить в классы, которые должны получить текущую дату. Замена DateTime.Now вызовом вашего объекта-оболочки.

В ваших тестах вы можете затем вставить заглушку или макет объекта, который дает вам необходимую дату.

Другое решение может быть, в зависимости от того, как работает ваш код, просто передать дату в качестве параметра вместо того, чтобы скрывать вызовы datetime.Now. Это делает ваш код немного более пригодным для повторного использования, потому что он может работать больше текущей даты

3 голосов
/ 19 февраля 2009

вы можете сделать макет объекта, который имитирует DateTime.Now

вот статья с примером, где насмехается над DateTime.Now: http://geekswithblogs.net/AzamSharp/archive/2008/04/27/121695.aspx

2 голосов
/ 19 февраля 2009
public interface ITimeProvider {
    DateTime Now { get; }
}

public class SystemTimeProvider : ITimeProvider {
    public virtual DateTime Now { get { return DateTime.Now; } }
}

public class MockTimeProvider : ITimeProvider {
    public virtual DateTime Now { get; set; }
}

public class ServiceThatDependsOnTime {
    protected virtual ITimeProvider { get; set; }
    public ServiceThatDependsOnTime(ITimeProvider timeProvider) {
        TimeProvider = timeProvider;
    }
    public virtual void DoSomeTimeDependentOperation() {
        var now = TimeProvider.Now;
        //use 'now' in some complex processing.
    }
}

[Test]
public virtual void TestTheService() {
    var time = new MockTimeProvider();
    time.Now = DateTime.Now.AddDays(-500);
    var service = new ServiceThatDependsOnTime(time);
    service.DoSomeTimeDependentOperation();
    //check that the service did it right.
}
1 голос
/ 21 января 2014

Почему бы не использовать подделки? С Visual Studio это максимально просто. Просто добавьте поддельную сборку в System, добавьте ShimsContext в ваш тест,

using (ShimsContext.Create())
{

И установите DateTime.Now, чтобы возвратить определенное значение:

System.Fakes.ShimDateTime.NowGet = () => new DateTime(2000, 1, 1);

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

0 голосов
/ 19 февраля 2009

Внедрение зависимости может быть решением для этого, да.

Что вы будете делать, это, например, создать интерфейс IDateTimeProvider с методом: GetDate (). В вашем классе производственного кода вы будете реализовывать возвращаемый DateTime.Now.

При модульном тестировании, как предложил Натриум, вы можете заменить его фиктивным объектом, который возвращает определенную дату для тестирования.

...