Стратегии работы с DateTime.Now в модульных тестах - PullRequest
3 голосов
/ 04 января 2011

У меня есть бизнес-логика, которая не выполняет определенные функции в субботу или воскресенье. Я хочу, чтобы мои модульные тесты подтвердили выполнение этих функций, но в субботу / воскресенье они не пройдут.

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

С C # / NUnit ...

Assert.That(
  DateTime.Now.DayOfWeek != DayOfWeek.Sunday && DateTime.Now.DayOfWeek !=      
  DayOfWeek.Saturday, "This test cannot be run on Saturday or Sunday");

Можно ли / желательно посоветовать дату? Есть ли другие стратегии для обработки этого сценария?

Ответы [ 2 ]

9 голосов
/ 04 января 2011

Можно / желательно ли попытаться смоделировать дату?

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

public bool Foo()
{
    return (DateTime.Now.DayOfWeek == DayOfWeek.Sunday);
}

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

Теперь рассмотрим это улучшение (разделение задач с помощью конструктора DI):

private readonly Func<DateTime> _nowProvider;
public SomeClass(Func<DateTime> nowProvider)
{
    _nowProvider = nowProvider;
}

public bool Foo()
{
    return (_nowProvider().DayOfWeek == DayOfWeek.Sunday);
}

Теперь это намного лучше ипроще для модульного тестирования.SomeClass больше не зависит от недетерминированной даты.В реальном приложении вы, очевидно, создадите экземпляр SomeClass, например, так:

var s = new SomeClass(() => DateTime.Now);
s.Foo();

, а в модульном тесте вы будете имитировать его, чтобы проверить оба случая:

var subjectUnderTest = new SomeClass(() => new DateTime(2011, 1, 3));
var actual = subjectUnderTest.Foo();
// assertions, ...
2 голосов
/ 04 января 2011

Вы должны создать абстракцию по системным часам, как вы должны создать абстракции по другим системным ресурсам и внешним ресурсам, чтобы иметь возможность писать RTM-модульные тесты .

Абстракция над системными часами довольно проста и может выглядеть следующим образом:

public interface ISystemClock
{
    DateTime Now { get; }
}

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

public class FakeSystemClock : ISystemClock
{
    // Note the setter.
    public DateTime Now { get; set; }
}

Теперь вы можете использовать эти фальшивые часы в своих юнит-тестах следующим образом:

[TestMethod]
[ExpectedException(typeof(InvalidOperationException),
    "Service should throw an exception when called on saturday.")]
public void DoSomething_WhenCalledOnSaturday_ThrowsException()
{
    // Arrange
    var saturday = new DateTime(2011, 1, 1);
    Assert.AreEqual(saturday.DayOfWeek, DayOfWeek.Saturday,
        "Test setup fail");
    var clock = new FakeSystemClock() { Now = saturday };
    var service = new Service(clock);

    // Act
    service.DoSomething();
}

В своем проекте приложения вы можете определить реализацию ISystemClock, которая фактически использует системные часы. Используя данное определение ISystemClock, оно будет выглядеть так:

public class RealSystemClock : ISystemClock
{
    public DateTime Now => DateTime.Now;
}

Когда вы используете контейнер DI, вы можете настроить его для подключения типов и автоматически вставлять экземпляры RealSystemClock, когда конструктор задает аргумент ISystemClock.

кстати. В то время как вы могли бы использовать Func<DateTime> делегатов, чтобы сделать трюк, определение интерфейса для этого намного более явное и намного более читаемое для других разработчиков. Кроме того, связать все в DI-контейнере было бы намного проще, потому что так легко получить несколько Func<DateTime> зависимостей, которые все делают иначе, чем "дают мне текущее местное время".

Надеюсь, это поможет.

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