为一个现在代表的DateTime制作一个包装器是一个好主意吗?
我最近注意到,使用表示’now’的DateTime作为方法的输入参数,用于模拟和测试目的非常好。 而不是每个调用DateTime.UtcNow
方法本身,我在上层方法中执行一次,然后在下层方法中执行。
所以很多需要’now’的方法现在都有一个输入参数DateTime now
。
(我正在使用MVC,并尝试检测一个名为now的参数和modelbind DateTime.UtcNow到它)
所以代替:
public bool IsStarted { get { return StartTime >= DateTime.UtcNow; } }
我通常有:
public bool IsStarted(DateTime now) { return StartTime >= now; }
所以我的约定是,如果一个方法有一个名为now
的DateTime
参数,你必须用当前时间来提供它。 当然这归结为惯例,而其他人可以很容易地在其中抛出一些其他DateTime作为参数。
为了使它更加可靠和静态类型,我正在考虑将DateTime包装在一个新对象中,即DateTimeNow。 因此,在最上面的一个层中,我将DateTime
转换为DateTime
,当有人试图在正常的DateTime中进行操作时,我们将得到编译错误。
当然你仍然可以解决这个问题,但至少如果你觉得你做错了什么的话。 有没有人进过这条道路? 从长远来看,我没有考虑过任何好的或坏的结果吗?
您可以创建具有必要属性的接口。 即IClock
并将其作为dependency injection。
interface IClock { DateTime Now { get; } DateTime UtcNow { get; } } class SystemClock : IClock { public DateTime Now { get { return DateTime.Now; } } public DateTime UtcNow { get { return DateTime.UtcNow ; } } } class TestDoubleClock : IClock { public DateTime Now { get { return whateverTime; } } public DateTime UtcNow { get { return whateverTime ; } } }
这样,您可以轻松地对依赖于DateTime
代码进行unit testing。 传递DateTime.Now
随处可见参数听起来很糟糕。 如果您需要Now
以及UtcNow
和其他内容怎么办? 你会为此单独添加三个参数吗?
我建议使用这种接口技术来避免使用过多参数的丑陋代码,这些参数对您没有多大帮助。
我建议创建一个提供Now值的接口:
public interface IDateTimeProvider { DateTime Now { get; } }
然后,如果要在MVC应用程序中使用当前日期,只需实现如下所示的类:
public class CurrentDateTimeProvider : IDateTimeProvider { public DateTime Now { get { return DateTime.Now; } } }
然后,您可以将其注入控制器,在unit testing中进行模拟,甚至替换为其他实现(例如,如果您决定在代码中使用UtcNow而不是Now
是的,如您所述,将自定义类型的不同计时器实现包装起来是个好主意。
我看到传递时间对象的三个缺点:
- 准确性:
请记住,给定函数的时间精度将取决于自第一次调用DateTime.Now
以来执行代码所需的时间。现在:
var date = DateTime.Now func_1(date) // took 1 second to execute func_2(date) // took 1 second to execute funf_3(date) // date is now late by 3 seconds.
- 性能:
您为每个函数添加一个参数,并在获取时间(通过包装器)时添加间接。 在我的情况下,这种开销在某些情况下是显而易见的。
- 安全:
除非您只允许一种具体类型封装时间,否则您将无法阻止客户端代码编写自己的时间包装器,仍然可以为您的函数提供他们想要的任何时间。
我通常通过让类接受一个nowProvider
作为nowProvider
一部分来解决这个问题,如下所示:
public class ClassToTest { private readonly Func _nowProvider; public ClassToTest(Func nowProvider) { _nowProvider = nowProvider; } public ClassToTest() { _nowProvider = () => DateTime.Now; } //snip }
所以在我的测试中我可以这样做:
var knownDate = new DateTime(2000, 1, 1); var testObject = new ClassToTest(() => knownDate);