高级unit testing和模拟对象的价值
我开始相信unit testing需要广泛使用模拟对象的高层次,写得很好的代码几乎没有价值。 我想知道这个说法是正确的,还是我错过了什么?
我是什么意思,高层次? 这些是食物链顶端的类别和function。 他们的input和输出往往是用户input和用户界面。 他们的大部分工作都是通过用户input和对下级实体的一系列调用来实现的。 他们通常没有或没有有意义的回报值。
写得好是什么意思? 在这种情况下,我指的是与其依赖关系解耦的代码(使用接口和dependency injection),并且逐行处于一致的抽象级别。 没有棘手的algorithm,条件很less。
我讨厌为这种代码编写unit testing。 unit testing几乎完全由模拟对象设置组成。 一行一行,unit testing几乎就像实现的镜像一样。 实际上,我通过查看实现来编写unit testing。 “首先我声明这个模拟方法被调用,然后我声明这个模拟方法被称为…”等。 我应该testing方法的行为,而不是它调用正确的方法序列 。 另一件事:我发现这些testing对于重构来说是非常脆弱的 。 如果一个testing如此脆弱,那么当被测代码被重构时,它会完全破碎并且必须被重写,那么unit testing的主要优点之一就不会丢失了吗?
我不希望这个职位被标记为议论,或不是一个问题。 所以我会直接陈述我的问题:unit testing我所描述的代码types的正确方法是什么?或者是不是所有的东西都需要unit testing ?
根据我的经验,你的代码的底层(简单而言)越低,unit testing的价值就越高,相对于编写它们所需的努力而言。 随着食物链变得越来越高,testing越来越复杂,越来越昂贵。
unit testing是至关重要的,因为它们告诉你什么时候在重构过程中破坏某些东西。
更高级别的testing有自己的价值,但是不再被称为unit testing; 他们被称为集成testing和验收testing。 集成testing是必要的,因为它们告诉你不同的软件组件如何协同工作。
验收testing是客户签收的。 验收testing通常由其他人员(而非程序员)编写,以提供不同的视angular; 程序员倾向于编写testing工作,testing人员试图通过testing什么不起作用来打破testing。
嘲笑只对unit testing有用。 对于集成和验收testing来说,嘲笑是没有用的,因为它没有运用实际的系统组件,比如数据库和通信基础设施。
一边
只需触及您的粗体语句:
“我应该testing方法的行为,而不是调用正确的方法序列”
被测对象的行为是它所需要的一系列操作。 这实际上是“行为”testing,而当你说“方法的行为”时,我认为你的意思是有状态testing,就像在给它一个input并validation正确的输出。
我做了这样的区分,因为一些BDD纯粹主义者甚至认为,testing你的class级应该调用什么更有意义,而不是input和输出是什么,因为如果你完全知道你的系统如何performance,那么你的input和输出将是正确的。
一个回应
除此之外,我个人从不为UI层编写全面的testing。 如果您正在为您的应用程序使用MVVM,MVP或MVC模式,那么在“1开发人员团队”级别,对于我来说这是令人头痛的麻烦和反作用。 我可以看到用户界面中的错误,是的,在这个级别的嘲笑行为往往是脆弱的。 我更关心确保我的底层域和DAL层正常运行。
顶层有什么价值是一个综合testing。 有一个networking应用程序? 不要断言你的控制器方法正在返回一个ActionResult(testing值很小),写一个集成testing,请求你的应用程序的所有页面,并确保没有404或403的。 在每次部署时运行一次。
最终答案
我总是遵循80/20规则进行unit testing 。 为了获得最高的20%的覆盖率,你正在谈论的是80%的努力。 对于我个人和大多数我的工作项目来说,这并没有得到回报。
总之,我同意。 我会写集成testing,并忽略你所描述的代码的unit testing。
我认为这是高度依赖于环境。 如果你在相对较小的团队中,并且可以保持testing的完整性,那么应用程序中更复杂的部分应该有unit testing。 根据我的经验,在大型团队中保持testing的完整性是相当困难的,因为testing一开始就没问题,直到他们不可避免地破裂为止。在这一点上,他们或者是a)“固定”,完全否定他们的有用性,或者b )及时注释掉了。
模拟testing的主要观点似乎是这样,pipe理人员可以声称代码覆盖率指标在Foo%….所以一切都必须正常工作! 一个可能有用的例外情况是当你需要testing一个真正重新创build的巨大痛苦的类时(例如在Struts中testing一个Action类)。
我是编写原始testing的信徒。 真正的代码,与真实的对象。 方法内部的代码会随着时间而改变,但是目的和整体行为通常不会。
如果做TDD,你不应该在实现之后编写testing,而应该是相反的。 这样你也可以避免使testing符合书面代码的问题。 您可能必须testing这些单位内的某些方法调用,而不是testing它们的顺序(如果它不是域问题 – 业务stream程的必要条件)。
有时候不要为某种方法编写testing是完全可行的。
一般来说,我认为testing这种types的方法/命令对于集成testing级别是成熟的。 具体来说,我“unit testing”的小,低级别的命令,(一般)没有副作用。 如果我真的想unit testing一些不适合这个模型的东西,我所做的第一件事就是看我能否重构/重新devise以适应它。
在较高的集成(和/或系统)testing级别上,我进入了有副作用的testing。 我尝试尽可能less的嘲弄(可能只有外部资源)在这一点上。 一个例子是嘲笑数据库层:
- logging如何调用它来获取数据
- 返回jar头数据logging它是如何
- logging如何调用插入操作的数据