如何在单元测试中模拟DateTime.Now?

Unk*_*own 8 c# unit-testing mocking

通常的解决方案是将其隐藏在界面后面.

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
    }
}
Run Code Online (Sandbox Code Playgroud)

在单元测试中,您可以使用模拟对象并决定返回什么

[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...
    }
}
Run Code Online (Sandbox Code Playgroud)

我不想在我的课程中注入另一个界面来获取当前时间.对于如此小的问题,感觉太重了.解决方案是使用具有公共功能的静态类.

public static class SystemTime
{
    public static Func<DateTime> Now = () => DateTime.Now;
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以删除ISystemTime注入,RecordService就像这样

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

    // process the record
    }
}
Run Code Online (Sandbox Code Playgroud)

在单元测试中,我们可以轻松地模拟系统时间.

[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...
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,所有这些都有不利之处.你正在使用公共字段(The HORROR!),所以没有人阻止你编写这样的代码.

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

此外,我认为教育开发人员比创建抽象更好,只是为了保护他们不犯任何错误.其他可能的问题与运行测试有关.如果您忘记将功能恢复到原始状态,则可能会影响其他测试.这取决于单元测试运行器执行测试的方式.您可以使用相同的逻辑来模拟文件系统操作

public static class FileSystem
{
    public static Action<string, string> MoveFile = File.Move;
}
Run Code Online (Sandbox Code Playgroud)

在我看来,使用公共函数实现这种功能(模拟时间,简单的文件系统操作)是完全可以接受的.它使代码更容易阅读,减少了依赖性,并且很容易在单元测试中进行模拟.

dar*_*yal 3

您不需要手动实现这一点。您可以使用 Moles 框架来执行此操作。 频道 9

  • 和最小起订量..以及许多其他模拟框架:-) (2认同)