2012-03-30 13 views
8

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.

+1

Czy użyłeś moq? (lub dowolne inne szydercze środowisko) http://code.google.com/p/moq/ – Magrangs

+0

Poprawiłem formatowanie Twojego kodu. Porównaj to ze swoją wersją, aby dowiedzieć się, jak zrobić to poprawnie. –

+1

@Magrangs: Jak wszystkie inne "normalne" ramy szydercze, Moq nie może wyśmiać statycznych metod i właściwości, takich jak 'DateTime.Now'. –

Odpowiedz

3

Nie musisz tego wdrażać ręcznie. W tym celu możesz użyć struktury Moles. channel 9

+2

I moq .. i wiele innych szyderczych frameworków :-) – Magrangs

+0

W rzeczywistości to pytanie mnie zadziwiło, ponieważ pierwszym przykładem moli w filmie jest to, jak łatwo kpić DateTime.Now :) – daryal

+1

@Magrangs: Not correct. Zobacz moją odpowiedź na inny komentarz powyżej. –

3

Nie powiedziałbym, że uzyskanie aktualnego czasu było tak małym zadaniem, a jeśli aplikacja staje się międzynarodowa i jest używana w wielu strefach czasowych, w której strefie czasowej uzyskujesz aktualny czas, najprawdopodobniej chcesz wspólna strefa czasowa do użycia, bez względu na lokalizację?

Interfejs umożliwia streszczenie tej wiedzy; Rozważałbym przywrócenie "bieżącego" czasu jako dość skomplikowanego zadania.

0

Biorąc pod uwagę, że mówimy o idiomatycznym C#, nie bardzo rozumiem problemy z ceremonią za interfejsem. W gruncie rzeczy masz TimeProvider, którą wstrzykniesz do konstruktora jako zależność i dostarczysz skrót do niego, aby sterować logiką odpowiedniego testowanego kodu.

Nie wierzę, że twoje inne podejścia zapewniają korzyści i mają negatywny aspekt, że nie są idiomatyczne, jak również eliminują użyteczne aspekty zastrzyku zależności (dokumentowanie zależności, szyderstwa, odwrócenie kontroli itp.)

Powiązane problemy