考虑单元测试字典对象。您可能编写的第一个单元测试只是将一些项添加到字典中并检查异常。下一个测试可能类似于测试计数是否准确,或者字典返回正确的键或值列表。
但是,这些后续案例中的每一个都要求字典可以首先可靠地添加项目。如果添加项目的测试失败,我们不知道我们后来的测试是否因为他们正在测试的内容不正确而失败,或者因为我们可以可靠地添加项目的假设是不正确的。
我是否可以声明一组单元测试,如果其中任何一个测试失败,导致某个单元测试无法确定?如果没有,我应该如何最好地解决这个问题?我是否错误地设置了我的单元测试,我遇到了这种困境?
答案 0 :(得分:3)
它并不像看起来那么难。让我们重新解释一下这个问题:
如果我测试了需要
System.Collections.Generic.List<T>.Add
工作的代码段,那么当有一天Microsoft决定在.Add
上暂停List<T>
时,我该怎么办?我是否根据这个进行测试才能确定?
上述答案显而易见; 你没有。你让它们失败的原因很简单 - 你的假设失败了,而测试应该失败。这里也一样。一旦你的添加测试工作,从那时起你假设添加工作。您不应该将测试的代码与第三方测试的代码区别对待。一旦它被证明有效,你就会认为确实有效。
另外,您可以使用名为 保护断言 的概念。在您的删除测试中,在安排阶段之后,您将引入额外的断言阶段,该阶段验证您的初始假设(在这种情况下 - 添加正在工作)。有关此技术的更多信息可以在here找到。
要添加示例,NUnit使用名称为Theory的伪装概念。这完全符合您的建议(但它似乎与数据驱动测试而不是一般效用更相关):
理论本身有责任确保所提供的所有数据都符合其假设。它通过使用Assume.That(...)构造来实现这一点,它就像Assert.That(...)但不会导致失败。如果特定测试用例不满足该假设,则该情况将返回不确定结果,而不是成功或失败。
但是,我认为Mark Seemann states in an answer对我所关联的问题最有意义:
对于给定的测试用例,可能需要满足许多前提条件,因此您可能需要多个Guard Assertion。不是在所有测试中重复这些测试,而是对每个前提条件进行一次(和唯一一次)测试,这样可以使测试代码更具可持续性,因为这样可以减少重复次数。
答案 1 :(得分:1)
好问题,我经常思考这个问题并且前几天遇到了这个问题。我所做的是使用幕后的字典来获取我们收藏的基础知识。例如:
public class MyCollection
{
private IDictionary<string, int> backingStore;
public MyCollection(IDictionary<string, int> backingStore)
{
_backingStore = backingStore;
}
}
然后我们测试推动了添加实现。正如我们通过引用获得字典一样,我们可以断言在添加项目之后我们的业务逻辑是正确的。
例如,additon的伪代码类似于:
public void Add(Item item)
{
// Check we have not added before
// More business logic...
// Add
}
然后可以编写测试,例如:
var subject = new MyCollection(backingStore);
subject.Add(new Item())
Assert.That(backingStore.Contains(itemThatWeAdded)
然后我们继续推出其他方法,例如检索和删除。
你的问题是你应该怎么做关于加法打破,反过来打破检索。这是一个捕获22场景。就个人而言,我宁愿抛弃后备存储并将其用作实现细节。所以这就是我们所做的。我们重构了测试以使用被测系统,而不是断言的后备存储。关于后备存储最初公开的好处是它允许你测试驱动代码库的一小部分,而不是一次性实现添加和检索。
在我们重构集合以不暴露后备存储之后,添加测试看起来像以下内容。
var subject = new MyCollection();
var item = new Item()
subject.Add(item)
Assert.That(subject.Has(item), Is.True);
在这种情况下,我认为这很好。如果您无法成功添加项目,那么您确定无法检索任何内容,因为您尚未添加它们。只要您的测试被命名良好,任何看到某些测试的开发人员(例如“CanOnlyAddUniqueItemsToCollection
”都会指向未来的开发人员正确的方向,换句话说,添加会被破坏。只要确保您的测试命名良好,您应该尽可能多地提供帮助。
答案 2 :(得分:0)
我不认为这是一个太大的问题。如果您的Dictionary类不是太大,并且该类的单元测试是测试该代码的唯一单元测试,那么当您的add方法被破坏并且多个测试失败时,您仍然知道问题在Dictionary类中并且可以识别它,轻松调试和修复它。
当问题出现时,如果您有其他代码气味或设计问题,例如:
答案 3 :(得分:-1)
这非常有趣。我们使用NUnit,我可以告诉它按字母顺序运行测试方法。这可能是一种过于人为的方式来订购测试,但是如果你构建了测试类,那么首先按字母/数字命名的pre-req方法就可以实现你想要的。
我发现自己编写了一个测试方法,只是为了观察它失败,然后编写代码使其通过。当我完成所有操作后,我可以运行整个类,一切都通过了 - 测试运行的顺序并不重要,因为一切都“有效”,因为我做了增量开发。
现在稍后,如果我在测试的东西中打破了什么,谁知道什么会在线束中失败。我想这对我来说并不重要 - 我有很多失败的故事,我可以弄清楚出了什么问题。