У меня есть код (С#), который полагается на сегодняшнюю дату, чтобы правильно рассчитать вещи в будущем. Если я использую сегодня дату в тестировании, я должен повторить вычисление в тесте, что не кажется правильным. Какой лучший способ установить дату в известное значение в тесте, чтобы я мог проверить, что результат является известным значением?
Какой хороший способ перезаписать DateTime.Now во время тестирования?
Ответ 1
Мое предпочтение состоит в том, чтобы классы, использующие время, фактически полагались на интерфейс, например
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
Ayende Rahien использует статический метод, который довольно прост...
public static class SystemTime
{
public static Func<DateTime> Now = () => DateTime.Now;
}
Ответ 3
Я думаю, что создание отдельного класса часов для чего-то простого, например, получение текущей даты немного переборщило.
Вы можете передать сегодняшнюю дату в качестве параметра, чтобы вы могли ввести другую дату в тесте. Это имеет дополнительное преимущество, делая ваш код более гибким.
Ответ 4
Использование Microsoft Fakes для создания прокладки - очень простой способ сделать это. Предположим, что у меня был следующий класс:
public class MyClass
{
public string WhatsTheTime()
{
return DateTime.Now.ToString();
}
}
В Visual Studio 2012 вы можете добавить сборку подделок в свой тестовый проект, щелкнув правой кнопкой мыши на сборке, которую вы хотите создать Fakes/Shims for, и выбрав "Add 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);
}
}
}
}
Ответ 5
Ключом к успешному тестированию устройства является развязка. Вы должны отделить свой интересный код от его внешних зависимостей, чтобы его можно было тестировать изолированно. (К счастью, Test-Driven Development производит развязанный код.)
В этом случае ваш внешний текущий DateTime.
Мой совет заключается в том, чтобы извлечь логику, связанную с DateTime, с новым методом или классом или с тем, что имеет смысл в вашем случае, и передать DateTime. Теперь ваш unit test может передавать произвольную дату Date, дают предсказуемые результаты.
Ответ 6
Другой, использующий Microsoft Moles (Изоляционная среда для .NET).
MDateTime.NowGet = () => new DateTime(2000, 1, 1);
Moles позволяет заменить любой .NET. метод с делегатом. Поддержка кротов статические или не виртуальные методы. Кроты опирается на профилировщика из Pex.
Ответ 7
Я бы предложил использовать шаблон 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/
Ответ 8
Простой ответ: ditch System.DateTime:) Вместо этого используйте NodaTime и он тестирует библиотеку: NodaTime.Testing.
Дальнейшее чтение:
Ответ 9
Вы можете ввести класс (лучше: метод / делегат), который вы используете для DateTime.Now
в тестируемом классе. Устанавливаем DateTime.Now
значение по умолчанию и устанавливаем его только при тестировании на фиктивный метод, который возвращает постоянное значение.
РЕДАКТИРОВАТЬ: Что сказал Блэр Конрад (у него есть код для просмотра). Кроме того, я предпочитаю делегатов для этого, так как они не загромождают вашу иерархию классов такими вещами, как IClock
...
Ответ 10
Мне нравится ваше решение @Blair.
Injecting System.DateTime не мешает вашим конкретным реализациям, но IClock или IDateTime позволяют вам издеваться над вашими модульными тестами.
Вот извлеченный интерфейс большинства открытых элементов System.DateTime, которые могут вам пригодиться: IDateTime
Ответ 11
Рассматривали ли вы использование условной компиляции для управления тем, что происходит во время отладки/развертывания?
например.
DateTime date;
#if DEBUG
date = new DateTime(2008, 09, 04);
#else
date = DateTime.Now;
#endif
В противном случае вы хотите разоблачить свойство, чтобы вы могли манипулировать им, это все, что связано с проблемой написания тестового кода, который я сейчас борется: D
Изменить
Большая часть меня предпочла бы подход Блэра. Это позволяет вам "горячо подключать" части кода, чтобы помочь в тестировании. Все это следует за принципом дизайна, заключающим в себе то, что изменяет код теста, ничем не отличается от производственного кода, его просто никто никогда не видит снаружи.
Создание и интерфейс могут показаться для этого примера большой работой (именно поэтому я выбрал условную компиляцию).