【问题标题】:Difficulty comparing two DateTime instances难以比较两个 DateTime 实例
【发布时间】:2012-08-09 11:48:29
【问题描述】:

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

FeedbackDao feedbackDao = new FeedbackDao();
// a couple of lines go here then I set up  this test:
Assert.IsTrue(feedbackDao.FeedbackDate.CompareTo(DateTime.Now) < 0);

我的假设是 feedbackDao.FeedbackDate 应该总是比DateTime.Now 返回的当前时间早一点,即使它只是一毫秒,我的IsTrue 测试应该总是通过,但有时它会通过并且有时它会失败。当我添加这样的消息时:

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

消息有时显示为 -1(意味着 FeedbackDate 早于 Now),有时显示为 0(意味着 DateTime 实例相等)。

为什么FeedbackDate总是早于Now?而且,如果我不能相信这个比较,我怎么能写一个严格的测试来检查 FeedbackDate 在构造 FeedbackDao 时的值?

【问题讨论】:

  • 为什么在地球不只是检查feedbackDao.FeedbackDate &gt; DateTime.Now或其他什么?
  • 如果我自己能想到这个问题,我为什么要问这个问题。这就是 Stackoverflow 出现的原因,不是吗?
  • @Paddy 谢谢。这回答了关于让我满意的严格测试的问题。我已经运行了好几次,它总是通过,让我确信属性设置正确。
  • 请注意,另一个测试,您可以在其中允许日期相等。 Jon Skeet 的回答还指出了您的测试设计中的一个重大缺陷,您一定要检查一下。
  • &lt;&lt;= 的测试根本不应该给你任何信心。如果没有人初始化该字段,其默认值将是DateTime.MinValue,它始终通过测试。在仍然使用DateTime.Now 对其进行初始化时,请参阅我的答案。

标签: c# .net unit-testing


【解决方案1】:

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

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

此外,DateTime.Now 的实际粒度仅为 10-15 毫秒 IIRC,并且经常如果您快速连续调用两次 DateTime.Now,您将得到相同的结果值两次。

出于可测试性的目的 - 以及清晰地表达依赖关系 - 我喜欢使用“时钟”接口 (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);

【讨论】:

  • 对不起,乔恩,我的意思不是真正的毫秒。我真的不知道实际的刻度是什么。也感谢您收紧我的代码片段。这将使我在下次必须比较 DateTime 实例时充满信心。
  • 显然,我为这样一个(典型的)有用的答案投了赞成票,但是 João Angelo 关于检查 FeedbackDate 字段是否晚于默认最小值 DateTime.Now 的观点给了我一个有很多值得深思的地方,我想我已经得到了我想要的测试。所以我接受了他的回答。
【解决方案2】:

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

此测试与测试feedbackDao.FeebackDate.CompareTo(DateTime.Now) &lt;= 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);

【讨论】:

  • 已经有很多有用的答案和 cmets,但是你关于该字段被初始化的可能性的观点,真的让我思考。恐怕我真的不明白什么是“模拟”框架,我不清楚“提取对 DateTime 的依赖”是什么意思。如果不诉诸“假货”,就没有办法严格地编写我的测试吗? Paddy 的评论或 NPSF3000 的答案与 FeedbackDate 大于 MinValue 的测试相结合就足够了,不是吗?
  • 我用我认为稍微好一点的替代方法更新了我的答案。
  • 为什么使用&gt;= 之类的运算符而不是CompareTo?它们更可靠吗?
  • 它们更简洁,并且更具可读性,在这种情况下,我只需要一个布尔结果,因此不需要 CompareToCompareTo 在排序场景中更有用。
  • 好吧,你得到了我的投票。它每次都会过去,因为它应该。我对var 有点惊讶。多年来我一直坚持使用 .NET 2.0,最近才升级到 .NET 4.5,所以我没有注意到 var
【解决方案3】:

在进行单元测试时,我认为DateTime.Now 是一个外部依赖项,因此需要模拟一些东西。我过去在测试涉及DateTime.Now 的场景时所做的,我刚刚通过类的构造函数传递了一个Func&lt;DateTime&gt;,它允许我在测试期间模拟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));

【讨论】:

  • 感谢 DBM。我是单元测试的新手,不知道在这种情况下“模拟”是什么意思,以及您解释它的方式,我现在更好地理解了依赖项的含义。 (我知道“依赖”是什么意思——我的意思是我现在更好地理解了 Jon 和 João 在这种情况下的意思。)谢谢。
【解决方案4】:

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

【讨论】:

  • 谢谢。模拟数据的整个概念正是我在考虑单元测试的方式中所缺少的。这是我的第一次尝试,所以这里的答案非常有用。
【解决方案5】:

试试

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

或者

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

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

【讨论】:

    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

      • 这听起来很有用。我会研究一下秒表。
      • 秒表也有它的问题。例如,在某些硬件上,当您的线程被安排在不同的内核上时,它会向前和向后跳跃。
      • @CodesInChaos 感谢您的警告。
      猜你喜欢
      • 2014-01-18
      • 1970-01-01
      • 2018-08-30
      • 2013-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多