单元测试:测试什么/不测试什么?

时间:2012-01-24 11:12:14

标签: unit-testing tdd

几天前,我开始对C#和VS2010中的单元测试和TDD感兴趣。我已经阅读了博客文章,观看过youtube教程,以及更多解释为什么TDD和单元测试对你的代码如此有用以及如何做到这一点的东西。

但我发现的最大问题是,我不知道在我的测试中检查什么以及不检查什么。

我理解我应该检查所有逻辑操作,引用和依赖项的问题,但是,例如,我应该为字符串格式创建一个单元测试,这是通过用户输入的吗?或者只是浪费我的时间,而我可以在实际代码中查看它?

有没有指导澄清这个问题?

8 个答案:

答案 0 :(得分:18)

在TDD中,每行代码都必须通过在代码之前写入的失败测试用例来证明。

这意味着在没有测试用例的情况下,您无法开发任何代码。如果你有一行代码(条件,分支,赋值,表达式,常量等),可以修改或删除而不会导致任何测试失败,这意味着这行代码是无用的,应该是已删除(或者您缺少测试以支持其存在)。

这有点极端,但这就是TDD的工作原理。话虽这么说如果你有一段代码并且想知道它是否应该被测试,你就没有正确地进行TDD。但是如果你有一个字符串格式化例程或变量增量或任何一小段代码,必须是支持它的测试用例。

更新( Ed。建议的用例):

  

例如,将一个对象添加到一个列表并创建一个测试,以查看它是否真的在里面,或者当列表不允许它时存在重复。

这是一个反例,你会惊讶地发现复制粘贴错误有多难以及它们有多常见:

private Set<String> inclusions = new HashSet<String>();
private Set<String> exclusions = new HashSet<String>();

public void include(String item) {
    inclusions.add(item);
}

public void exclude(String item) {
    inclusions.add(item);
}

另一方面,单独测试include()exclude()方法是一种矫枉过正,因为它们本身并不代表任何用例。但是,它们可能是某些业务用例的一部分,您应该进行测试。

显然,您不应该在分配后测试x中的x = 7是否真的7。测试生成的getter / setter也是一种过度杀伤力。但它是最容易破解的代码。通常由于复制和粘贴错误或拼写错误(特别是在动态语言中)。

另见:

答案 1 :(得分:14)

您的前几个TDD项目可能会导致更糟糕的设计 /重新设计,需要更长时间来完成您的学习(至少根据我的经验)。这就是为什么你不应该在大型关键项目上使用TDD。

我的建议是在少数小项目(100-10,000 LOC)上使用“纯”TDD (接受/单元测试所有测试优先)。您可以自己进行辅助项目,也可以在空闲时间进行编码,在小型内部实用程序中使用TDD来完成工作。

在大约6-12个项目上进行“纯粹”TDD之后,您将开始了解TDD如何影响设计并学习如何设计可测试性。一旦你知道如何设计可测试性,你将需要更少的TDD和最大化单位,回归,验收等测试的ROI ,而不是预先测试所有内容。

对我来说,TDD更多的是用于良好代码设计的教学方法,而不是实用的方法。但是,我仍然使用TDD逻辑代码和单元测试而不是调试。

答案 2 :(得分:5)

这个问题没有简单的答案。行动中有收益递减规律,因此实现完美覆盖很少值得。知道要测试什么是经验,而不是规则。最好有意识地评估流程。什么东西坏了?测试是否可行?如果没有,是否可以重写代码以使其更易于测试?是否值得在将来一直测试此类案例?

如果将代码拆分为模型,视图和控制器,您会发现大多数关键代码都在模型中,而且这些代码应该是相当可测试的。 (这是MVC的要点之一。)如果一段代码是关键的,我会对它进行测试,即使这意味着我必须重写它以使其更易于测试。如果一段代码容易出错或被未来的更新破坏,它就会得到一个测试。我很少测试控制器和视图,因为它不值得为我带来麻烦。

答案 3 :(得分:4)

我看到你的所有代码的方式属于三个桶中的一个:

  1. 易于测试的代码:这包括您自己的确定性公共方法。
  2. 难以测试的代码:这包括GUI,非确定性方法,私有方法以及具有复杂设置的方法。
  3. 您不想测试的代码:这包括第三方代码,以及难以测试且不值得努力的代码。
  4. 在这三个中,您应该专注于测试简单代码。测试代码的难度应该重构为两部分:您不想测试的代码和简单的代码。当然,您应该测试重构的简单代码。

答案 4 :(得分:3)

在极限编程解释中,肯特贝克说,你只需要测试生产中需要工作的东西。

这是封装测试驱动开发的一种粗暴方式,其中生产代码中的每个更改都由不存在更改时失败的测试支持;并且你不需要它,它表示为仅处理几个特定情况的应用程序创建通用类没有任何价值。

答案 5 :(得分:2)

我认为你应该只测试入口点到系统的行为。这包括公共方法,公共访问器和公共字段,但不包括常量(常量字段,枚举,方法等)。它还包括任何直接处理IO的代码,我将在下面进一步说明原因。

我的推理如下:

  1. 所有公开的内容基本上都是系统行为的切入点。因此,应编写单元测试,以确保该入口点的预期行为按要求工作。您不应该测试调用入口点的所有可能方法,只测试您明确要求的方法。因此,您的单元测试也是系统支持的行为规范以及如何使用它的文档。

  2. 非公开的东西基本上可以随意删除/重新考虑,而不会影响系统的行为。如果你要测试它们,你将从单元测试到该代码创建一个硬依赖,这将阻止你对它进行重构。这就是为什么除了公共方法,字段和访问器之外你不应该测试任何其他东西。

  3. 设计的常数不是行为,而是公理。验证常量的单元测试本身就是一个常量,所以它只是重复的代码和无用的工作来编写常量测试。

  4. 所以回答你的具体例子:

      

    我应该为字符串格式化创建一个单元测试   用户输入?

    是的,绝对的。接收或发送外部输入/输出(可以总结为接收IO)的所有方法都应进行单元测试。这可能是唯一一个我认为接收IO的非公开事物也应该进行单元测试的情况。那是因为我认为IO是一个公共条目。任何我认为是公开的外部演员的入口点。

    因此,单元测试公共方法,公共字段,公共访问器,即使这些是静态构造,也可以单元测试从外部actor接收或发送数据的任何内容,无论是用户,数据库,协议等。 / p>

    注意:您可以在非公开事物上编写临时单元测试,以帮助确保您的实施正常运行。这是一种帮助您弄清楚如何正确实现它的方法,并确保您的实现按预期工作。在您测试它之后,您应该删除单元测试或从测试套件中禁用它。

答案 6 :(得分:1)

我认为你必须改变自己的观点。 在纯粹形式中,TDD需要红绿重构工作流程:

  • 写测试(必须失败)RED
  • 编写代码以满足测试绿色
  • 重构您的代码

所以问题是“我需要测试什么?”有一个响应,如:“你必须编写一个与功能或特定要求相对应的测试”。

通过这种方式,您可以获得必须的代码覆盖率以及更好的代码设计(请记住,TDD也代表测试驱动的“设计”)。

一般来说,您必须测试所有公共方法/接口。

答案 7 :(得分:0)

  

我应该为字符串格式化创建一个单元测试   用户输入?或者只是在我可以检查时浪费我的时间   它在实际代码中?

不确定我明白你的意思,但你在TDD中编写的测试应该测试你的生产代码。它们不是检查用户输入的测试。

换句话说,可以有TDD单元测试来测试用户输入验证代码,但是不能有TDD单元测试来验证用户输入本身。