Подтвердить что ты не робот

Код модульного тестирования, который обрабатывает дату на основе сегодняшней даты

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

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

Как вы можете unit test такой код?

Является ли это тем, что Injection Dependency становится очень полезным?

Изменить

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

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

4b9b3361

Ответ 1

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

Это зависимость и неопределенная зависимость от этого. Вам нужно еще немного разделить ответственность за код.

До:

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

После:

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

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

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

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 6

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

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

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

Ответ 7

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.
}

Ответ 8

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

using (ShimsContext.Create())
{

И установите DateTime.Now для возврата определенного значения:

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

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

Ответ 9

Инъекция зависимостей может быть решением для этого, да.

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

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