写点什么

“TDD 就是死亡”?我要为单元测试辩护

作者:Guilherme Ferreira

  • 2023-01-09
    北京
  • 本文字数:2831 字

    阅读完需:约 9 分钟

“TDD就是死亡”?我要为单元测试辩护

在 2014 年的时候,David Heinemeier Hansson在软件开发界引起了轩然大波。他在RailsConf的台上公然宣布“TDD就是死亡”。

 

这是个大胆的举动,但他也成为了很多不满于测试的人所寻找的领头人。很多人选择了跟随,开发者们就此分成了两个阵营。

 

当时所掀起的新浪潮一路带我们到了今天,单元测试不再重要,集成测试占据上风。Mike Cohn 所提出的著名测试金字塔如今被重塑为菱形形状。推动这股浪潮的因素并不唯一,在对现有测试实践不满的背后,或许并不难找到许多原因。

 

这种情况常常伴随固化的实践传播、缺乏的正确指导,以及沉溺的抽象思维出现。人人都试图做到最好,不断尝试、失败,再尝试,直到有人能打破锁链,开拓不同的道路,一条承诺通往低维护性测试套组的道路。

 


什么是最好的方向?


即使是在新领域,我们也很容易遗忘历史,这是我在这个行业中所学到的事。飞速发展的步伐让我们相信过去没有答案,而未来充满奇迹。我不知道未来会如何,但我相信我们总会倾向于追寻创新而非寻找信息。

 

或许下面这些问题能让很多质疑用测试菱形替代金字塔的声音:

  • 问题是由单元测试或单元测试编写所引起的吗?

  • 集成测试是否应用于需要的组件?

  • 我的误解是否导致了多处相同的断言?

  • 我是通过测试优化设计,还是围绕现有设计进行测试?

 

寻根  


答案或许又一次藏在了过去。

 

那么在集成测试上,历史能告诉我们什么呢?历史中,集成测试是不同开发单元一同测试的阶段。这些单元独立开发,且通常来自不同团队。在这一阶段,我们要确保的是所有接口的实现和运行正常。

 

如今的集成测试常常用于同一团队所开发的代码单元,这就意味着每个源码文件都是一个系统边界,相当于每个代码文件都是由同一个自主团队开发一样,模糊了单元测试和集成测试之间的界限。

 

因此,我们可以断定,集成测试与单元测试之间区别的根源是个错误。认定集成测试是用于团队之间测试,而单元测试是用于单一团队测试,这种区分观念本身便是不对的。我们所要解决的果正是自己造下的因。

 

我们应当明确这二者的界限,明确开发团队之间的界限而非层次。界限的明确会让你从新的角度看待系统的角色,以及它与其他领域的交互。类似于 Alistair Cockburn 所描述的六边形架构,后者也被称作是“端口与适配器”。在他的描述中,系统拥有里外两个面,而我们要做的,就是通过明确定义的边界来连接这两个面。

 

这有什么用呢?正是这个内外层的关系,明确了单元测试与集成测试之间的关系。单元测试负责从外向内地测试界限,而集成测试则是从内向外地对边界测试。具体来说,集成测试确保了适配器、网关和客户端,这些负责连接其他开发单元(如 API、插件、、数据库和模块)之间关系的正常运作。

 

针对行为的测试


“单元测试”中的“单元”是什么?单元是指行为的单元。这段定义中完全没有提到过任何针对单一文件、对象,或者函数的测试。那么,为什么编写针对行为的测试很难?

 

多数测试类型都会遇到的问题来自软件结构和测试之间的紧密联系,其中开发者忽视了测试目标,并以透明盒(有时也被称作是白盒)的方式应对测试。透明盒测试是指内部设计时以系统正常运作为目标,常见于单元测试。但透明盒的问题在于,测试往往会过于细化导致产生大量用例,而这些用例又与底层结构紧密耦合,给维护增加了难度。

 

人们对单元测试的不爽部分来源于此。而集成测试由于更多地脱离底层的设计,受重构的影响往往比单元测试要小。

 

我更倾向于从另一个角度看问题。这一点是集成测试的优势,还是由透明盒测试所带来的问题?如果我们以基于行为的不透明盒(有时也被称作黑盒)方式进行单元测试,那么结果是否一样或更好?

 

人们常常以为不透明盒测试只能应用于系统的外部边界,但这是不对的。我们的系统是由许多边界组成,有些可以借助通信协议访问,有些可以通过进程中的适配器扩展。这些适配器都有自己的边界,可以通过基于行为的方式进行测试。

 

模拟:孤注一掷


透明盒测试通常大量使用模拟(mock),可一旦过度依赖模拟,测试便会更难维护。也许这就是 Mark Seemann 所说的“桩(stub)和模拟破坏了封装”。在正视这类由过度使用模拟所带来的问题后,厌恶、甚至不惜一切代价地避免模拟是很正常的。仅使用 API 的测试通常会导致对模拟的过度依赖。

 

但问题又回到了最初,这是否真的是由模拟或对模拟的误用导致的呢?

 

模拟和桩或许更难维护,但它们的存在是有意义的。它们让测试更快也更稳定,我们需要掌控它们,且不在必要之外滥用模拟和桩。

 

减少代码的公共表面


透明盒测试的另一弊端在于其会导致更多代码的暴露。验证器、映射器,以及其他可能的内部实现细节都会因为测试的缘故暴露在公共契约(contract)内。任何使用 Java 或 C#的人都知道接口在代码库中的普遍程度。仅仅为了模拟一个依赖,开发者很可能会引入一个新接口。

 

可被从外部访问的代码将会更难变动,测试也将成为必须项,让代码的可维护性受到质疑,在没有大量重写单元测试的情况下,重构几乎成为不可能。乍一看,这一问题似乎利于集成测试;集成测试更关注于外层,不会暴露很多的实现细节。

 

但还是那个问题,这究竟是单元测试的问题,还是我们在实现单元测试方式的问题?如果我们以不透明盒的形式进行单元测试,无视内部设计方式,仅关注于消费者需求,或许我们会收获更小的契约,一个更易于测试的契约,更少的测试量,以及更便于维护的测试。

 

以架构为指导原则


测试常常是围绕架构进行的。我们常常在系统设计完成之后才会考虑测试,但这也会让系统测试的难度更上一层楼。多层架构中这种情况屡见不鲜,对数据访问技术的依赖让领域层的单元测试更加繁复。

 

采用测试隔离的架构可以轻松避免这种问题。无论是六边形架构还是干净架构(Clean Architecture),可选的有很多。这类架构的构建是独立于设备的,所有基础设备依赖都会通过依赖配置接入系统。架构的单元测试会更加舒适,并引导集成测试到适当的应用下:测试连往外部的适配器。

 

集成测试的适配器会为我们的测试策略带来一个薄弱点。在所有组件相连的情况下进行集成测试,将收获测试在配置和组合方面的优势,显然这是我们想要的目标。当然我们也可以继续在组件全部相连的情况下测试,但这些测试只会是“烟雾弹”,不需要测试所有的边界情况,从而导向更稳定,也更可靠的测试。

 

结论


质疑行业中的固有观念很重要,但在质疑之前先充分理解这些观念也同样重要。

 

我们都知道历史是不断重复的,过去会带给我们关于将来决定的信息。但我们也应该明白同样的错误将会不可避免地一遍遍发生。这是人之本性,是否能规避这些错误将取决于我们自己。测试策略也是这些不断重复的错误之一。我们要面对这些由于信息或知识匮乏,以及错失已有优秀实践而导致的痛点。

 

测试与架构是息息相关的。我们需要在设计架构时不忘测试情况,在我们追寻优秀测试策略时,单元测试仍会是我们可用的工具。


原文链接:

https://www.infoq.com/articles/unit-tests-testing-pyramid/


相关阅读:

个性化测试流程,不搞一刀切

只擅长构建软件是不够的,我们必须擅长构建可测试的软件 | QCon

2023-01-09 08:005799

评论

发布
暂无评论
发现更多内容

架构实战营模块2课后作业

hello

架构实战营

回帖送大奖 『和AI在一起』

百度大脑

人工智能 活动 大奖

Go语言:参数传递中,值、引用及指针之间的区别

微客鸟窝

Go 语言

为什么“内存管理”漏洞值得你的绝对关注!

鉴释

代码审查 内存 代码

耗时半年,堪称奇迹!阿里架构师整合出258W字Java全栈面试题

钟奕礼

Java 程序员 架构 面试 计算机

因聚而生 | 图扑受邀参加“生态融合,智创未来”大会

一只数据鲸鱼

数据可视化 数字孪生 智慧工业 智能制造

深度分享|金融行业模型管理效能提升的规划与思考

索信达控股

大数据 金融科技 金融 风险管理 营销管理

JAVA语言异步非阻塞设计模式(应用篇)

有道技术团队

后端 网易有道

字节大牛的1850页Leetcode刷题笔记外泄!用实力折服众人

进击的王小二

Java 面试 算法 LeetCode

给需要关心安全的技术人员的一些建议

鉴释

网络安全 安全

新时代程序员都用什么写代码?

程序员鱼皮

Java Python 大前端 Web 开发工具

从零开始学习3D可视化之数据对接(2)

ThingJS数字孪生引擎

大前端 数据 可视化 数字孪生

真的强!来自扫地僧总结的39W字上千道Java一线大厂面试题手册,成功助我拿下蚂蚁金服offer!

钟奕礼

Java 编程 程序员 架构 面试

PancakeSwap交易所市值管理机器人开发

Geek_23f0c3

市值管理机器人开发 PancakeSwap交易所 交易所机器人

华为云MVP程云:知识化转型,最终要赋能一线

华为云开发者联盟

人工智能 自然语言处理 机器学习 华为云 智能问答机器人

音视频的这些功能你知道吗?

anyRTC开发者

音视频 屏幕共享 智能降噪

支持60+数据传输链路,华为云DRS链路商用大盘点

华为云开发者联盟

华为云 DRS

赖建新:关于静态代码分析的问与答

鉴释

静态代码分析

iOS面试·一个iOS程序员的BAT面试全记录(内含百度+网易+阿里面试真题)

iOSer

ios 面试 iOS 知识体系

互操作性和去信任化的两难困境,到底需要治标还是治本?

CECBC

关于单元测试的那些事儿,Mockito 都能帮你解决

华为云开发者联盟

测试 Mockito Mock Java 开发 模拟测试框架

网络攻防学习笔记 Day82

穿过生命散发芬芳

网络攻防 7月日更

ES本地debug详解

泽睿

ES 搜索引擎;

鉴释陈新中:源代码安全在物联网时代的重要性

鉴释

物联网 源代码

不保护数据的代价!

鉴释

数据 数据安全

Camtasia入门技巧之视频剪辑

淋雨

视频剪辑 Camtasia 录屏软件

漏洞非小事,金融服务机构如何对抗代码缺陷?

鉴释

金融科技 代码安全检测

阿里内部最新出炉“SpringCloudAlibaba笔记”号称微服务界的里程碑!

Java 编程 架构 微服务 计算机

从京东零售云走出来的3D数字人正在触动未来的互动世界

数字经济时代下,区块链如何助力数字产业发展?

旺链科技

区块链 分布式存储 数字经济

阿里集团业务驱动的升级 —— 聊一聊Dubbo 3.0 的演进思路

阿里巴巴中间件

云计算 阿里云 云原生 dubbo 中间件

“TDD就是死亡”?我要为单元测试辩护_软件工程_InfoQ精选文章