Normalnym rozwiązaniem jest ukrycie go za interfejsem.Jak wyśmiać DateTime.Teraz w testach jednostkowych?
public class RecordService
{
private readonly ISystemTime systemTime;
public RecordService(ISystemTime systemTime)
{
this.systemTime = systemTime;
}
public void RouteRecord(Record record)
{
if (record.Created <
systemTime.CurrentTime().AddMonths(-2))
{
// process old record
}
// process the record
}
}
W badanej jednostki można użyć atrapa obiektu i zdecydować, co do powrotu
[TestClass]
public class When_old_record_is_processed
{
[TestMethod]
public void Then_it_is_moved_into_old_records_folder()
{
var systemTime = A.Fake<ISystemTime>();
A.CallTo(() => system.Time.CurrentTime())
.Returns(DateTime.Now.AddYears(-1));
var record = new Record(DateTime.Now);
var service = new RecordService(systemTime);
service.RouteRecord(record);
// Asserts...
}
}
Nie lubię wstrzyknąć inny interfejs do mojej klasy tak aby uzyskać aktualny czas. To wydaje się zbyt ciężkim rozwiązaniem dla tak małego problemu. Rozwiązaniem jest użycie klasy statycznej z funkcją publiczną.
public static class SystemTime
{
public static Func<DateTime> Now =() => DateTime.Now;
}
Teraz możemy usunąć zastrzyk ISystemTime i RecordService wygląda to
public class RecordService
{
public void RouteRecord(Record record)
{
if (record.Created < SystemTime.Now.AddMonths(-2))
{
// process old record
}
// process the record
}
}
w testach jednostkowych możemy mock czas systemowy tak łatwo.
[TestClass]
public class When_old_record_is_processed
{
[TestMethod]
public void Then_it_is_moved_into_old_records_folder()
{
SystemTime.Now =() => DateTime.Now.AddYears(-1);
var record = new Record(DateTime.Now);
var service = new RecordService();
service.RouteRecord(record);
// Asserts...
}
}
Oczywiście istnieje w tym wszystkim minus. Używasz pól publicznych (HORROR!), Więc nikt nie przestaje pisać takiego kodu.
public class RecordService
{
public void RouteRecord(Record record)
{
SystemTime.Now =() => DateTime.Now.AddYears(10);
}
}
Także myślę, że lepiej jest kształcenie programistów niż tworzyć abstrakcje tak, aby chronić je przed tym żadnych błędów. Inne możliwe problemy związane są z uruchomieniem testów. Jeśli zapomnisz przywrócić funkcję do pierwotnego stanu, może to wpłynąć na inne testy. Zależy to od sposobu, w jaki jednostka przeprowadzająca testy wykonuje testy. Możesz używać tej samej logiki, aby drwić operacji na systemie plików
public static class FileSystem
{
public static Action<string, string> MoveFile = File.Move;
}
Moim zdaniem wykonawczego tego rodzaju funkcjonalność (kpiącym czasu, proste operacje File System) przy użyciu funkcji publicznych jest całkowicie dopuszczalne. Sprawia, że kod jest łatwiejszy w czytaniu, zmniejsza zależności i łatwo jest ośmieszyć się w testach jednostkowych.
Czy użyłeś moq? (lub dowolne inne szydercze środowisko) http://code.google.com/p/moq/ – Magrangs
Poprawiłem formatowanie Twojego kodu. Porównaj to ze swoją wersją, aby dowiedzieć się, jak zrobić to poprawnie. –
@Magrangs: Jak wszystkie inne "normalne" ramy szydercze, Moq nie może wyśmiać statycznych metod i właściwości, takich jak 'DateTime.Now'. –