难以比较两个DateTime实例

时间:2012-08-09 11:48:30

标签: c# .net unit-testing

我正在用C#(.NET 4.5)编写一个单元测试类。在其中一个测试中,我在构造类FeedbackDao的实例后检查各种属性的值。在构建时,FeedbackDate的{​​{1}}属性设置为FeedbackDao

DateTime.Now

我的假设是feedbackDao.FeedbackDate应该总是比FeedbackDao feedbackDao = new FeedbackDao(); // a couple of lines go here then I set up this test: Assert.IsTrue(feedbackDao.FeedbackDate.CompareTo(DateTime.Now) < 0); 返回的当前时间早一点,即使它只有一毫秒,我的DateTime.Now测试应该总是通过,但是有时它会通过,有时会失败。当我添加这样的消息时:

IsTrue

消息有时读取-1(表示Assert.IsTrue(feedbackDao.FeedbackDate.CompareTo(DateTime.Now) < 0, feedbackDao.FeedbackDate.CompareTo(DateTime.Now).ToString()); 早于FeedbackDate),有时读取0(表示DateTime实例相等)。

为什么Now 总是<{1}}?而且,如果我不相信这种比较,我怎样才能编写一个严格的测试来检查构造FeedbackDateNow的价值?

7 个答案:

答案 0 :(得分:8)

  

我的假设是feedbackDao.FeebackDate应该总是比DateTime.Now返回的当前时间早一点,即使它只有一毫秒。

是什么让你这么想?这表明1000次调用必须采用至少1秒,这似乎不太可能。

除此之外,DateTime.Now的实际粒度大约为10-15ms IIRC,而经常,如果你快速连续两次拨打DateTime.Now' ll两次获得相同的值。

出于可测试性的目的 - 以及干净的依赖关系表达 - 我喜欢使用“时钟”接口(IClock),它总是用于提取当前系统时间。然后,您可以编写一个假实现来控制您认为合适的时间。

此外,这个断言是有缺陷的:

Assert.IsTrue(feedbackDao.FeebackDate.CompareTo(DateTime.Now) < 0,
                feedbackDao.FeebackDate.CompareTo(DateTime.Now).ToString());

它存在缺陷,因为它会对DateTime.Now进行两次评估...因此报告的值不一定与检查的值相同。它会更好:

DateTime now = DateTime.Now;
Assert.IsTrue(feedbackDao.FeebackDate.CompareTo(now) < 0,
                feedbackDao.FeebackDate.CompareTo(now).ToString());

甚至更好:

DateTime now = DateTime.Now;
DateTime feedbackDate = feedbackDao.FeebackDate;
Assert.IsTrue(now < feedbackDate,
              feedbackDate + " should be earlier than " + now);

答案 1 :(得分:1)

您的测试没有那么有用,因为它,您断言该值小于DateTime.Now但这并不意味着它已正确设置为预期值。如果日期时间未初始化,则会显示DateTime.MinValue值将始终通过测试

此测试与feedbackDao.FeebackDate.CompareTo(DateTime.Now) <= 0的测试一样有效,因此您不会遇到促使您撰写此问题的问题。

您需要提取对DateTime.Now的依赖关系或使用支持模拟DateTime.Now的模拟框架,并声明该值已初始化为正确的值。您可以检查Microsoft Moles,现在重命名为VS 2012中的Fakes,这是我知道的唯一免费的模拟框架(最新版本的一种,因为它附带VS并且不知道它是否是可在快速版本上使用),这样您就可以将来电替换为DateTime.Now

更新

如果不采用模拟框架,您可以通过执行以下操作来改善测试:

var lowerBoundary = DateTime.Now;
var dao = new FeedbackDao();
var upperBoundary = DateTime.Now;

Assert.IsTrue(dao.Date >= lowerBoundary && dao.Date <= upperBoundary);

答案 2 :(得分:1)

在进行单元测试时,我认为DateTime.Now是外部依赖,因此需要进行模拟。我在测试涉及DateTime.Now的方案时过去所做的,我刚刚通过类的构造函数传递了Func<DateTime>,这允许我在测试期间模拟DateTime.Now

我更喜欢Jon Skeet关于使用类似IClock接口的东西来包裹DateTime属性的建议,仅仅因为我上次这样做,我感到愚蠢地制作一个新的界面和类包裹一个属性。如果您需要测试多个静态DateTime属性,我绝对同意IClock建议。

例如,

public class Foo
{
    private readonly Func<DateTime> timeStampProvider;
    public Foo(Func<DateTime> timeStampProvider)
    {
        this.timeStampProvider = timeStampProvider;
    }

    public Foo() : this(() => DateTime.Now)
    {
    }

    public bool CompareDate(DateTime comparisonDate)
    {
        // Get my timestamp
        return comparisonDate > timeStampProvider();
    }
}

然后,在测试期间,

var testFoo = new Foo(() => new DateTime(1, 1, 2010));

答案 3 :(得分:1)

我通常使用模拟数据来验证我的逻辑。我围绕模拟数据改进了测试场景。正如DBM所建议的那样。 模拟数据是一组通常是静态或可配置的已知数据。通常的做法是使用包含所有测试数据的XML文件,并在需要时加载它们。我可以在我们的项目中给你一个例子。

答案 4 :(得分:0)

尝试

Assert.IsTrue(feedbackDao.FeebackDate.CompareTo(DateTime.Now) < 1);

或者

Assert.IsTrue(feedbackDao.FeebackDate - DateTime.Now < someMarginOfError);

时间通常相当精细 - 通常为10毫秒IIRC。

答案 5 :(得分:0)

根据您的系统,DateTime.Now不会每毫秒更新或打勾,它只会定期更新。通常为10毫秒左右。见这里:How frequent is DateTime.Now updated ? or is there a more precise API to get the current time?

答案 6 :(得分:0)

DateTime.Now不是100%准确。它增加了大约130毫秒(从每个蜱的个人经验)。因此,如果你的方法足够快,日期将等于datetime.now而不是更小,这很可能。 如果你想要一个100%准确的计时器,你应该使用StopWatch类 Msdn link to stopwatch