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

本文关键字:单元测试 DateTime 何模拟 模拟 | 更新日期: 2023-09-27 18:29:26

通常的解决方案是将其隐藏在接口后面。

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

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

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

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

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

您不需要手动实现。您可以使用Moles框架来完成此操作。通道9

我不会说获取当前时间是一项很小的任务,如果您的应用程序成为国际应用程序并在多个时区中使用,那么您获取的当前时间是哪个时区,很可能您希望使用一个通用时区,无论您的地区如何?

该界面允许您将这些知识抽象出来;我认为检索"当前"时间是一项相当复杂的任务。

考虑到我们谈论的是惯用c#,我不太理解接口后面的仪式的问题。本质上,您有一个TimeProvider,您将其作为依赖项注入到构造函数中,并为其提供一个存根来驱动测试中相关代码的逻辑。

我不认为你的其他方法有好处,它们有不习惯的负面影响,也消除了依赖注入的有用方面(记录依赖关系、模拟、控制反转等)