最近,有人给我描述了一个“时间机器”服务,也就是可以用来改变代码块对时间的感知方式的服务。
通常,通过调用DateTime.Now和其他DateTime静力学,时间直接与系统时钟联系在一起。使用时间机器服务代替静态DateTime调用,并允许外部代码更改其报告时间。这在为服务编写单元测试时尤其有用,这些服务由于计时器、触发器等原因而随时间的推移而不同。
我喜欢这个想法,并决定实现我自己的版本。以下是相关的接口:
// A service that can travel through time
public interface ITimeTraveler
{
ITimeMachine TimeMachine { get; set; }
}
// Interface for a Time Machine
public interface ITimeMachine
{
DateTime Now();
DateTime UtcNow();
DateTime Today();
DateTime UtcToday();
void TimeTravel(TimeSpan adjustment);
void TimeTravelTo(DateTime newDateTime);
void FreezeTime();
void UnfreezeTime();
void RevertAllTimeTravel();
bool IsCurrentlyTimeTraveling();
}下面是具有完整实现的时光机原理。
这包括两次修订。第一个修订是我最初链接到的代码,另外的修订考虑到了这里的反馈。
我正在寻找关于这个服务的概念和我目前的实现的任何反馈或建议。
发布于 2014-04-25 02:55:27
我喜欢它。
对于某些非常特殊的情况,类需要测试,其功能取决于System.DateTime.Now返回的内容,那么如果您希望能够编写完全控制该依赖项的单元测试,则需要一个抽象来包装静态方法调用。
你的ITimeMachine做得很好。虽然IDateTimeService是一个有意义的名称,但它似乎更适合泛化。ITimeTraveler可能是多余的。
相关代码如下所示:
public class MyClass
{
private readonly IDateTimeService _service;
public MyClass(IDateTimeService service)
{
_service = service;
}
public void DoSomething()
{
var now = _service.Now(); // instead of DateTime.Now;
//...
}
}我也喜欢你让Now()成为一种方法。它抽象出了一个事实,即DateTime.Now实际上是一种适用于属性的方法。
发布于 2014-04-25 04:32:49
我猜是:
因此,后一种方法不需要在这个接口中(传递给正在测试的代码)。这些方法可以放在单独的接口中(只有单元测试代码才知道),也可以只是TimeMachine类的方法。
发布于 2014-04-25 14:06:49
我的另一个答案只涉及原来的界面(S)。这一点突出了我在实现中注意到的一点。
/// <summary>
/// Retrieve the current date and time.
/// </summary>
public DateTime Now()
{
return FrozenDateTime != null
? FrozenDateTime.Value
: DateTime.Now.Add(Offset);
}
/// <summary>
/// Move to the specific point in time provided.
/// </summary>
/// <param name="newDateTime">The point in time to move to.</param>
public void TimeTravelTo(DateTime newDateTime)
{
Offset = newDateTime.Subtract(DateTime.Now);
}我很想看到它的作用,所以我写了一个小小的测试:
class Program
{
static void Main(string[] args)
{
var service = new TimeMachine();
Console.WriteLine("Current time is: {0} (system time is: {1})", service.Now(), DateTime.Now);
//service.FreezeTime();
//Console.WriteLine("Time frozen at: {0}", service.Now());
Thread.Sleep(2000);
Console.WriteLine("Slept for 2000ms.");
Console.WriteLine("Current time is: {0} (system time is: {1})", service.Now(), DateTime.Now);
service.TimeTravelTo(DateTime.Now.AddHours(1));
Console.WriteLine("Time-traveled 1 hour into the future.");
Console.WriteLine("Current time is: {0} (system time is: {1})", service.Now(), DateTime.Now);
service.UnfreezeTime();
Console.WriteLine("Time unfrozen.");
Console.WriteLine("Current time is: {0} (system time is: {1})", service.Now(), DateTime.Now);
Console.ReadLine();
}
}输出:

如果我取消对FreezeTime()调用的评论,我会得到以下内容:

在我看来,这就像一个bug,因为Sleep(2000)似乎从未发生过。
如果我将TimeTravelTo()的实现更改为使用类自身对Now的抽象:
/// <summary>
/// Move to the specific point in time provided.
/// </summary>
/// <param name="newDateTime">The point in time to move to.</param>
public void TimeTravelTo(DateTime newDateTime)
{
Offset = newDateTime.Subtract(Now());
}我明白了:

这对我来说更有意义(只要时间被冻结,Sleep(2000)就没有任何影响,但当您解除冻结时间时,偏移量又回到正轨上了)--但我可能错了,您的实现很可能正在做它应该做的事情。因此,我的观点不一定是您的代码中存在错误,而是您的XML文档应该更清楚地说明这种行为:解冻时间是否应该赶上实际的日期/时间?
https://codereview.stackexchange.com/questions/48100
复制相似问题