问题描述
我有一个持续的测试管道,其中单元和集成测试必须在代码能够合并之前传递 PR。
它在减少错误和稳定我们的代码库方面做得很好,但我们遇到了时间问题。
在周末,包含此堆栈的多个应用程序会进行明确检查,以防止在周末提交新订单。更糟糕的是,其中一些应用程序使用 DateTime.Now 进行这些检查,并且在没有大量重构的情况下无法模拟。
是否可以在 C# 中在我们的测试中发出一个命令来更新“服务器时间”,以便我们的大多数测试可以工作?
解决方法
简短的回答是否定的。如果你想调整一些基本的界面,你要么需要设置你的操作系统并在测试运行之前更改时间(这可能会产生非常不可预测的结果)或模拟它。其实很简单:
不要使用 DateTime.Now/UtcNow,而是使用您的显式版本。
public interface IDateTimeManager
{
DateTime Now {get;}
}
代码库中的任何位置:
var now = _dateTimeManager.Now;
在测试设置中:
var mockDateTimeManager = new Mock<IDateTimeManager>();
mockDateTimeManager.Setup(x=> x.Now).Returns(new DateTime(2000,1,1));
在设置中:
var container = new Container();
container.RegisterSingleton<IDateTimeManager>(mockDateTimeManager.Object);
如果你是应用程序独立的可执行文件/服务,你可以传递一个参数来设置这个管理器的初始值:
myapp.exe --start-date "2000-01-01"
public interface IDateTimeManager
{
DateTime UtcNow { get; }
}
internal sealed class DateTimeManager : IDateTimeManager
{
private readonly DateTime _initialTime;
private readonly DateTime _initialOsTime;
public DateTime UtcNow => _initialTime + (DateTime.UtcNow - _initialOsTime);
public DateTimeManager(DateTime initialTime)
{
_initialTime = initialTime;
_initialOsTime = DateTime.UtcNow;
}
}
附注
在这种情况下,控制反转是您最好的朋友。
我得出的结论是,将特定于操作系统的方法替换为我自己的接口更容易,而不是绕过测试环境。例如,在我的项目中,出于同样的原因,所有 File
命名空间经常被嘲笑。我可以通过这种方式模仿任何行为。
也就是说,使用适当的数据结构调用 WinAPI 函数 SetSystemTime
。
不要忘记将服务器时间返回“正确”!
[当然,假设您的服务器是 Windows,但您没有提及...]