我开始相信单元测试高级,编写良好的代码,需要大量使用模拟对象,几乎没有任何价值。我想知道这个断言是否正确,或者我错过了什么?
高层是什么意思?这些是靠近食物链顶端的类和功能。它们的输入和输出往往是用户输入和用户界面。他们的大部分工作包括获取用户输入并对较低级别的实体进行一系列调用。它们通常很少或没有有意义的回报值。
我的意思是写得好吗?在这种情况下,我指的是与其依赖关系解耦的代码(使用接口和依赖注入),并且逐行一致抽象程度。没有棘手的算法,也没有条件限制。
我讨厌为这种代码编写单元测试。单元测试几乎完全由模拟对象设置组成。逐行,单元测试读取几乎像执行镜像。实际上,我通过查看实现来编写单元测试。 “首先我断言这个模拟方法被调用,然后我断言这个模拟方法被调用......”等等。我应该测试方法的行为,而不是它调用正确的方法序列。另一件事:我发现这些测试非常脆弱,无法重构。如果测试非常脆弱,那么当被测代码被重构时,它就会彻底破碎并且必须重写,那么单元测试的主要好处之一是否已经丢失了?
我不希望这篇文章被标记为议论性的,或者不是问题。因此,我将直接说明我的问题:对我所描述的代码进行单元测试的正确方法是什么,或者是否理解并非所有内容都需要单元测试?
答案 0 :(得分:43)
根据我的经验,代码的较低级别(不是简单的),相对于编写它们所需的工作量,单元测试的价值就越高。随着食物链越来越高,测试越来越精细,越来越昂贵。
单元测试至关重要,因为它们会告诉您何时在重构期间破坏某些内容。
更高级别的测试有自己的价值,但之后它们不再被称为单元测试;它们被称为集成测试和验收测试。需要进行集成测试,因为它们会告诉您不同软件组件的协同工作情况。
验收测试是客户签署的。验收测试通常由其他人(不是程序员)编写,以提供不同的视角;程序员倾向于为可行的方法编写测试,测试人员试图通过测试不起作用来破解它。
模拟仅对单元测试有用。对于集成和验收测试,模拟是无用的,因为它不会运行实际的系统组件,例如数据库和通信基础架构。
答案 1 :(得分:20)
只需触摸你的粗体声明:
“我应该测试一下这种行为 方法,而不是它正在调用 正确的方法序列“
测试对象的行为是所采取的一系列行动。这实际上是“行为”测试,而当你说“方法的行为”时,我认为你的意思是有状态测试,就像在中,给它一个输入并验证正确的输出。
我之所以这样区别是因为一些BDD纯粹主义者甚至认为测试你的课程应该调用什么更有意义,而不是输入和输出是什么,因为如果你完全了解你的系统表现正常,那么你的输入和输出就是正确的。
除此之外,我个人从不为UI层编写全面的测试。如果您正在为您的应用程序使用MVVM,MVP或MVC模式,那么在“1开发人员团队”级别,这对我来说是麻木不仁并且适得其反。我可以看到 UI中的错误,是的,在这个级别的模拟行为往往是脆弱的。我更关心的是确保我的底层域和DAL层正常运行。
顶层的 值是集成测试。有一个网络应用程序?而不是断言您的控制器方法返回ActionResult(小值测试),编写一个集成测试,请求应用程序中的所有页面,并确保没有404或403。在每次部署时运行一次。
我始终遵循80/20规则进行单元测试 。要想达到你所谈论的最高20%的覆盖率,那将是你努力的80%。对于我的个人和我的大多数工作项目,这都没有得到回报。
简而言之,我同意。我会编写集成测试,并忽略您描述的代码的单元测试。
答案 2 :(得分:3)
我认为它高度依赖环境。如果您的团队规模相对较小,并且可以保持测试的完整性,那么应用程序中更复杂的部分应该进行单元测试。根据我的经验,在大型团队中保持测试完整性是相当困难的,因为测试最初都是正常的,直到它们不可避免地破坏...此时它们要么a)“固定”以完全否定其有用性的方式,或者b )及时评论出来。
模拟测试的要点似乎是管理者可以声称代码覆盖率指标是Foo%....所以一切都必须正常工作!它们可能有用的一个例外情况是当你需要测试一个真正重新创建的类时(例如在Struts中测试一个Action类)。
我非常相信编写原始测试。真实代码,真实对象。方法内部的代码会随着时间的推移而改变,但目的和整体行为通常都不会。
答案 3 :(得分:0)
如果做TDD,你不应该在实施后编写测试,而是反过来。这样您还可以避免使测试符合编写代码的问题。您可能必须在这些单元中测试某些方法调用,但不测试它们的顺序(如果不是域问题的必要 - 业务流程)。
有时,不为某种方法编写测试是完全可行的。
答案 4 :(得分:0)
通常,我认为测试这种类型的方法/命令对于集成测试级别来说已经成熟。具体来说,我对较小的低级命令进行“单元测试”,这些命令通常没有副作用。如果我真的想要对不适合该模型的东西进行单元测试,我首先要做的就是看看我是否可以重构/重新设计以使其适合。
在更高的集成(和/或系统)测试级别,我会对有副作用的事物进行测试。我尝试尽可能少地模拟(可能只有外部资源)。一个例子是将数据库层模拟为: