我正在用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}}?而且,如果我不相信这种比较,我怎样才能编写一个严格的测试来检查构造FeedbackDate
时Now
的价值?
答案 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