3

我正在用 C# (.NET 4.5) 编写单元测试类。在其中一个测试中,我在FeedbackDao构造我们的类的实例后检查各种属性的值。在构造时, 的FeedbackDate属性FeedbackDao设置为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?而且,如果我不能相信那个比较,我怎么能写一个严格的测试来检查FeedbackDatewhenFeedbackDao被构造的值?

4

7 回答 7

8

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

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

DateTime.Now除此之外,IIRC 的实际粒度只有大约 10-15 毫秒,而且通常如果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);
于 2012-08-09T11:51:30.087 回答
1

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

于 2012-08-10T08:01:51.163 回答
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);
于 2012-08-09T11:52:36.577 回答
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));
于 2012-08-09T12:37:18.553 回答
0

尝试

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

或者

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

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

于 2012-08-09T11:51:36.060 回答
0

根据您的系统,DateTime.Now不会每毫秒或每滴答一次更新,它只会定期更新。通常为 10 毫秒左右。请参阅此处:DateTime.Now 更新的频率如何?还是有更精确的 API 来获取当前时间?

于 2012-08-09T11:52:00.493 回答
0

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

于 2012-08-09T11:53:19.207 回答