14

作为 TDD 的新手,我正在努力编写处理集合的单元测试。例如,目前我正在尝试提出一些测试方案来基本上测试以下方法

int Find(List<T> list, Predicate<T> predicate);

list该方法应返回列表中与谓词匹配的第一项的索引predicate。到目前为止,我能够提出的唯一测试用例是

  • list不包含任何物品时- 返回-1
  • list包含 1 个匹配项时predicate- 返回0
  • list包含 1 个不匹配的项目时predicate- 返回-1
  • list包含两个都匹配的项目时predicate- 返回0
  • list包含 2 个项目时,第一个匹配predicate- 返回0
  • ETC...

正如您所看到的,这些测试用例数量众多,并且不能令人满意地测试我真正想要的实际行为。我心中的数学家想要做某种 TDD-by-induction

  • list不包含任何物品时- 返回-1
  • list包含 N 项时,调用predicate第一项,然后递归调用Find剩余的 N-1 项

然而,这引入了不必要的递归。对于上述方法,我应该在 TDD 中编写什么样的测试用例?


顺便说一句,我真正尝试测试的方法只是Find,仅针对特定的集合和谓词(我可以独立编写测试用例)。当然,我应该有一种方法可以避免编写上述任何测试用例,而是简单地测试该方法是否使用正确的参数调用了其他一些Find实现(例如)?FindIndex

请注意,无论如何,我仍然想知道如何进行单元测试Find(或其他类似的方法),即使事实证明在这种情况下我不需要。

4

5 回答 5

9

如果 find() 正在工作,那么它应该返回与谓词匹配的第一个元素的索引,对吗?

因此,您需要对空列表情况进行测试,对不匹配元素情况进行测试,对匹配元素情况进行测试。我会觉得这就足够了。在 TDDing find() 的过程中,我可能会编写一个特殊的第一个元素通过案例,我可以很容易地伪造它。我可能会写:

emptyListReturnsMinusOne()
singlePassingElementReturnsZero()
noPassingElementsReturnsMinusOne()
PassingElementMidlistReturnsItsIndex()

并期望该序列将推动我的正确实施。

于 2012-07-05T14:05:46.313 回答
2

当恐惧被无聊取代时停止测试——肯特贝克

在这种情况下,给定通过测试的概率是多少

  • “当列表包含 2 个都匹配谓词的项目时 - 返回 0”

下面的测试会失败吗?

  • “当列表包含 5 个都匹配谓词的项目时 - 返回 0”

我会写前者,因为我担心这种行为不适用于多个元素。但是,一旦 2 工作,为 5 编写另一个只是乏味(​​除非在生产代码中硬编码假设 2.. 它应该被重构掉。即使不是,我也只需将现有测试修改为有 5 而不是 2 并使其适用于一般情况)。

因此,为明显不同的事物编写测试。在这种情况下,列出(零、一、多个)元素和(包含/不包含)操作数

于 2012-07-06T05:53:03.273 回答
0

根据您对Find方法的要求,这是我要测试的内容:

  1. listis null- 抛出ArgumentNullException或返回-1
  2. list不包含任何物品 - 退货-1
  3. predicateis null- 抛出ArgumentNullException或返回-1
  4. listpredicate包含一项与- 返回不匹配的项目-1
  5. list包含一项与predicate- 返回匹配的项目0
  6. list包含多个项目,但没有项目匹配predicate- 返回-1
  7. list包含匹配的多个项目predicate- 返回第一个匹配项的索引

基本上,您将首先测试结束情况 - 空参数、空列表。之后,进行一项测试。最后,测试多个项目的匹配和不匹配。

对于null参数,您可以-1根据自己的喜好抛出异常或返回。

于 2012-07-05T14:08:29.610 回答
0

不要更改列表,更改谓词

考虑如何调用该方法。当有人调用该Find方法时,他们已经有了一个列表并且需要考虑谓词。所以想出一些很好的例子来证明 的行为Find

示例:对所有测试用例 使用相同的列表3, 4可以很容易理解:

  1. 谓词< 5匹配两个数字(返回1
  2. 谓词== 3匹配3(返回0
  3. 谓词== 0不匹配(返回-1

这实际上是您指定行为所需的全部内容,并且通过更改谓词而不是列表,您可以提供如何使用该Find方法的良好示例。包含零个、一个或两个元素的列表并没有真正改变方法的行为,Find也没有真正改变方法的使用方式。用你的测试用例遵循 DRY,专注于指定行为而不是证明代码是正确的,否则你最终将把所有的时间都花在编写测试上。

于 2012-07-05T14:14:00.273 回答
0

试着回答你的问题:我对 Rhino 模拟没有任何经验,但我相信它应该有类似于 FakeItEasy(?) 的东西:

var finder = A.Fake<IMyFindInterface>();

// ... insert code to call IMyFindInterface.Find(whatever) here

A.CallTo(() => finder.find(A<List>.That.Matches(
                  x => x.someProperty == someValue))).MustHaveHappened();

通过将 Find() 的实现放在接口后面,然后将使用该接口的方法传递一个假的,您可以检查该方法是否使用某些参数调用。(如果预期的调用未完成, MustHaveHappended()将导致测试失败)。

由于您知道IMyFindInterface的实际实现只是将调用传递给您已经信任的实现,因此这应该是一个足够好的测试来验证您正在测试的代码是否以正确的方式调用 Find 实现。

当您只想确保您的代码(您正在测试的单元)通过抽象出该组件本身以正确的方式调用您已经信任的某个组件时,可以使用相同的过程 - 这正是我们在单元测试时想要的。

于 2012-07-06T06:13:40.300 回答