2012-03-30 36 views
8

Giải pháp thông thường là ẩn nó sau giao diện.Làm thế nào để thử DateTime.Now trong các bài kiểm tra đơn vị?

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

Trong thử nghiệm đơn vị bạn có thể sử dụng đối tượng giả và quyết định những gì để trở

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

Tôi không muốn tiêm giao diện khác vào lớp học của tôi chỉ để có được thời gian hiện tại. Nó cảm thấy giải pháp quá nặng cho một vấn đề nhỏ như vậy. Giải pháp là sử dụng lớp tĩnh với chức năng công cộng.

public static class SystemTime 
{ 
    public static Func<DateTime> Now =() => DateTime.Now; 
} 

Bây giờ chúng ta có thể loại bỏ các tiêm ISystemTime và RecordService trông như thế này

public class RecordService 
{ 
    public void RouteRecord(Record record) 
    { 
     if (record.Created < SystemTime.Now.AddMonths(-2)) 
     { 
      // process old record 
     } 

    // process the record 
    } 
} 

Trong các bài kiểm tra đơn vị chúng ta có thể thử thời gian hệ thống giống như một cách dễ dàng.

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

Tất nhiên, có một nhược điểm đối với tất cả điều này. Bạn đang sử dụng các trường công cộng (The HORROR!) Để không ai dừng bạn viết mã như thế này.

public class RecordService 
{ 
    public void RouteRecord(Record record) 
    { 
     SystemTime.Now =() => DateTime.Now.AddYears(10); 
    } 
} 

Ngoài ra tôi nghĩ tốt hơn là giáo dục các nhà phát triển hơn là tạo trừu tượng chỉ để bảo vệ họ khỏi mắc lỗi. Các vấn đề khác có thể liên quan đến việc chạy thử nghiệm. Nếu bạn quên khôi phục chức năng về trạng thái ban đầu, nó có thể ảnh hưởng đến các thử nghiệm khác. Điều này phụ thuộc vào cách nhân vật thử nghiệm đơn vị thực hiện các bài kiểm tra. Bạn có thể sử dụng cùng một logic để thử hoạt động hệ thống tập tin

public static class FileSystem 
{ 
    public static Action<string, string> MoveFile = File.Move; 
} 

Theo tôi thực hiện loại hình chức năng (chế giễu thời gian, đơn giản hoạt động hệ thống tập tin) sử dụng các chức năng công cộng là hoàn toàn chấp nhận được. Nó làm cho mã dễ đọc hơn, giảm sự phụ thuộc và dễ dàng giả lập trong các bài kiểm tra đơn vị.

+1

Bạn đã sử dụng moq chưa? (hoặc bất kỳ khuôn khổ nhạo báng nào khác) http://code.google.com/p/moq/ – Magrangs

+0

Tôi đã sửa mã định dạng của bạn. Hãy so sánh nó với phiên bản của bạn để tìm hiểu làm thế nào để làm điều đó một cách chính xác. –

+1

@Magrangs: Như tất cả các khuôn khổ mocking "bình thường" khác, Moq không thể giả lập các phương thức tĩnh và các thuộc tính như 'DateTime.Now'. –

Trả lời

3

Bạn không cần thực hiện việc này bằng tay. Bạn có thể sử dụng khung Moles để làm điều này. channel 9

+2

Và moq .. và nhiều khung mocking khác :-) – Magrangs

+0

Trong thực tế, câu hỏi này làm tôi ngạc nhiên là ví dụ đầu tiên về các nốt ruồi trong video là cách dễ dàng để giả lập DateTime.Now :) – daryal

+1

@Magrangs: Không chính xác. Xem câu trả lời của tôi cho bình luận khác của bạn ở trên. –

3

Tôi không nói thời gian hiện tại là một nhiệm vụ nhỏ, nếu ứng dụng của bạn trở thành quốc tế và được sử dụng trong nhiều múi giờ, múi giờ nào bạn nhận được thời gian hiện tại, rất có thể bạn sẽ muốn sử dụng múi giờ chung, bất kể ngôn ngữ của bạn là gì?

Giao diện cho phép bạn tóm tắt kiến ​​thức này; Tôi sẽ xem xét lấy thời gian "hiện tại" như một nhiệm vụ khá phức tạp.

0

Xét rằng chúng ta đang nói về thành ngữ C#, tôi không hoàn toàn hiểu được vấn đề với buổi lễ sau giao diện. Về bản chất, bạn có một số TimeProvider mà bạn đưa vào hàm khởi tạo dưới dạng phụ thuộc và bạn cung cấp một sơ đồ cho nó để điều khiển logic của mã có liên quan đang được kiểm tra.

Tôi không tin rằng các cách tiếp cận khác của bạn mang lại lợi ích và chúng có khía cạnh tiêu cực không phải là thành ngữ cũng như loại bỏ các khía cạnh hữu ích của tiêm phụ thuộc (tài liệu phụ thuộc, chế nhạo, đảo ngược kiểm soát, v.v ...)

Các vấn đề liên quan