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ị.
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
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. –
@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'. –