正常的解决方案是将其隐藏在接口后面。如何在单元测试中模拟DateTime.Now?
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
}
}
在单元测试中,你可以使用模拟对象,并决定什么返回
[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...
}
}
我不喜欢注入另一个接口到我的课堂只是为了获得当前的时间。对于这样一个小问题,感觉过于沉重。解决方案是使用静态类与公共功能。
public static class SystemTime
{
public static Func<DateTime> Now =() => DateTime.Now;
}
现在我们可以删除ISystemTime注射RecordService看起来像这样
public class RecordService
{
public void RouteRecord(Record record)
{
if (record.Created < SystemTime.Now.AddMonths(-2))
{
// process old record
}
// process the record
}
}
在单元测试中,我们可以很容易地嘲笑系统时间。
[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...
}
}
当然,这一切都有一个缺点。您正在使用公共字段(The HORROR!),因此没有人阻止您编写这样的代码。
public class RecordService
{
public void RouteRecord(Record record)
{
SystemTime.Now =() => DateTime.Now.AddYears(10);
}
}
另外我认为,教育开发人员比创建抽象是为了保护他们免于犯错。其他可能的问题与运行测试有关。如果您忘记将功能恢复到原始状态,则可能会影响其他测试。这取决于单元测试运行器执行测试的方式。 您可以使用相同的逻辑来嘲笑文件系统操作
public static class FileSystem
{
public static Action<string, string> MoveFile = File.Move;
}
在实施这种利用公共职能的功能(嘲讽的时候,简单的文件系统操作),我的意见是完全可以接受的。它使得代码更易于阅读,减少了依赖性,并且很容易在单元测试中模拟。
你用过moq吗? (或任何其他模拟框架)http://code.google.com/p/moq/ – Magrangs 2012-03-30 11:38:04
我修正了您的代码的格式。请将它与您的版本进行比较,以了解如何正确执行此操作。 – 2012-03-30 11:41:04
@Magrangs:与所有其他“正常”嘲讽框架一样,Moq不能嘲讽像“DateTime.Now”这样的静态方法和属性。 – 2012-03-30 11:42:07