几天前,我开始对C#和VS2010中的单元测试和TDD感兴趣。我已经阅读了博客文章,观看过youtube教程,以及更多解释为什么TDD和单元测试对你的代码如此有用以及如何做到这一点的东西。
但我发现的最大问题是,我不知道在我的测试中检查什么以及不检查什么。
我理解我应该检查所有逻辑操作,引用和依赖项的问题,但是,例如,我应该为字符串格式创建一个单元测试,这是通过用户输入的吗?或者只是浪费我的时间,而我可以在实际代码中查看它?
有没有指导澄清这个问题?
答案 0 :(得分:18)
在TDD中,每行代码都必须通过在代码之前写入的失败测试用例来证明。
这意味着在没有测试用例的情况下,您无法开发任何代码。如果你有一行代码(条件,分支,赋值,表达式,常量等),可以修改或删除而不会导致任何测试失败,这意味着这行代码是无用的,应该是已删除(或者您缺少测试以支持其存在)。
这有点极端,但这就是TDD的工作原理。话虽这么说如果你有一段代码并且想知道它是否应该被测试,你就没有正确地进行TDD。但是如果你有一个字符串格式化例程或变量增量或任何一小段代码,必须是支持它的测试用例。
例如,将一个对象添加到一个列表并创建一个测试,以查看它是否真的在里面,或者当列表不允许它时存在重复。
这是一个反例,你会惊讶地发现复制粘贴错误有多难以及它们有多常见:
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)
我看到你的所有代码的方式属于三个桶中的一个:
在这三个中,您应该专注于测试简单代码。测试代码的难度应该重构为两部分:您不想测试的代码和简单的代码。当然,您应该测试重构的简单代码。
答案 4 :(得分:3)
这是封装测试驱动开发的一种粗暴方式,其中生产代码中的每个更改都由不存在更改时失败的测试支持;并且你不需要它,它表示为仅处理几个特定情况的应用程序创建通用类没有任何价值。
答案 5 :(得分:2)
我认为你应该只测试入口点到系统的行为。这包括公共方法,公共访问器和公共字段,但不包括常量(常量字段,枚举,方法等)。它还包括任何直接处理IO的代码,我将在下面进一步说明原因。
我的推理如下:
所有公开的内容基本上都是系统行为的切入点。因此,应编写单元测试,以确保该入口点的预期行为按要求工作。您不应该测试调用入口点的所有可能方法,只测试您明确要求的方法。因此,您的单元测试也是系统支持的行为规范以及如何使用它的文档。
非公开的东西基本上可以随意删除/重新考虑,而不会影响系统的行为。如果你要测试它们,你将从单元测试到该代码创建一个硬依赖,这将阻止你对它进行重构。这就是为什么除了公共方法,字段和访问器之外你不应该测试任何其他东西。
设计的常数不是行为,而是公理。验证常量的单元测试本身就是一个常量,所以它只是重复的代码和无用的工作来编写常量测试。
所以回答你的具体例子:
我应该为字符串格式化创建一个单元测试 用户输入?
是的,绝对的。接收或发送外部输入/输出(可以总结为接收IO)的所有方法都应进行单元测试。这可能是唯一一个我认为接收IO的非公开事物也应该进行单元测试的情况。那是因为我认为IO是一个公共条目。任何我认为是公开的外部演员的入口点。
因此,单元测试公共方法,公共字段,公共访问器,即使这些是静态构造,也可以单元测试从外部actor接收或发送数据的任何内容,无论是用户,数据库,协议等。 / p>
注意:您可以在非公开事物上编写临时单元测试,以帮助确保您的实施正常运行。这是一种帮助您弄清楚如何正确实现它的方法,并确保您的实现按预期工作。在您测试它之后,您应该删除单元测试或从测试套件中禁用它。
答案 6 :(得分:1)
我认为你必须改变自己的观点。 在纯粹形式中,TDD需要红绿重构工作流程:
所以问题是“我需要测试什么?”有一个响应,如:“你必须编写一个与功能或特定要求相对应的测试”。
通过这种方式,您可以获得必须的代码覆盖率以及更好的代码设计(请记住,TDD也代表测试驱动的“设计”)。
一般来说,您必须测试所有公共方法/接口。
答案 7 :(得分:0)
我应该为字符串格式化创建一个单元测试 用户输入?或者只是在我可以检查时浪费我的时间 它在实际代码中?
不确定我明白你的意思,但你在TDD中编写的测试应该测试你的生产代码。它们不是检查用户输入的测试。
换句话说,可以有TDD单元测试来测试用户输入验证代码,但是不能有TDD单元测试来验证用户输入本身。