16 | 为什么你的测试不够好?
郑晔
该思维导图由 AI 生成,仅供参考
你好!我是郑晔。今天是除夕,我在这里给大家拜年了,祝大家在新的一年里,开发越做越顺利!
关于测试,我们前面讲了很多,比如:开发者应该写测试;要写可测的代码;要想做好 TDD,先要做好任务分解,我还带你进行了实战操作,完整地分解了一个任务。
但有一个关于测试的重要话题,我们始终还没聊,那就是测试应该写成什么样。今天我就来说说怎么把测试写好。
你或许会说,这很简单啊,前面不都讲过了吗?不就是用测试框架写代码吗?其实,理论上来说,还真应该就是这么简单,但现实情况却往往相反。我看到过很多团队在测试上出现过各种各样的问题,比如:
测试不稳定,这次能过,下次过不了;
有时候是一个测试要测的东西很简单,测试周边的依赖很多,搭建环境就需要很长的时间;
这个测试要运行,必须等到另外一个测试运行结束;
……
如果你也在工作中遇到过类似的问题,那你理解的写测试和我理解的写测试可能不是一回事,那问题出在哪呢?
为什么你的测试不够好呢?
主要是因为这些测试不够简单。只有将复杂的测试拆分成简单的测试,测试才有可能做好。
简单的测试
测试为什么要简单呢?有一个很有趣的逻辑,不知道你想没想过,测试的作用是什么?显然,它是用来保证代码的正确性。随之而来的一个问题是,谁来保证测试的正确性?
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
本文从测试的简单性出发,探讨了测试的写作方式。作者指出,简单的测试是好测试的基础,而一个简单的测试应该包括前置准备、执行、断言和清理四个部分。此外,文章还提到了常见的测试“坏味道”,如执行部分过于复杂、缺乏断言以及测试本身过于复杂等问题。作者强调了测试的简单性和清晰性对于保证代码正确性的重要性,鼓励读者多写简单的测试来覆盖不同的场景,而不是试图在一个测试中覆盖过多的功能。通过本文,读者可以了解到如何写出简单、清晰、有效的测试,以及避免常见的测试陷阱。文章还介绍了A-TRIP五个单词的缩写,分别是Automatic(自动化)、Thorough(全面)、Repeatable(可重复的)、Independent(独立的)和 Professional(专业的),这些特点是衡量好测试的标准。最后,作者强调了写好测试的关键在于写简单的测试,鼓励读者分享对测试的新理解。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《10x 程序员工作法》,新⼈⾸单¥68
《10x 程序员工作法》,新⼈⾸单¥68
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(36)
- 最新
- 精选
- 西西弗与卡夫卡可重复特别重要,有些开发在本地测和数据库相关应用时,由于前置依赖数据比较多,为了避免测试前写冗长的数据准备代码,所以会预先在数据库中准备好初始数据。每个测试再初始化特定的数据,因为Spring测试框架可以自动回滚,所以在本地是可以重复跑的。但是,放到CI中时,测试就统统没法过了,因为CI的数据库是共用的,没有本地的那份初始化数据集。一种方式是,保持数据库干净,用测试时用初始化脚本准备数据。如果测的场景比较复杂,比如要测多个事务的交互结果,还可以引入Docker,将依赖的数据库及初始化数据做成Docker的image,测试代码就更加简单,并且可以重复运行了,只要CI支持Docker即可
作者回复: 很好的分享!
2019-02-04531 - 朱国伟我还是习惯先写代码 在写测试 如 有一个投资机会详情(opportunities/{id})的功能 首先是大的步骤任务拆解 - 查询机会基本信息 - 查询机会参与方(标的方、投资方)信息 - 相似推荐 即推荐与该机会类似的机会 - Domain ==> VO 然后是每个大步骤再细分 如 - 查询机会基本信息 - 调用机会Service getById() 获取基本信息 - 调用机会Mappper getById() 获取基本信息 - 机会Mapper.xml 写查询sql - 机会Mapper单元测试 - 准备机会数据 setUp/before中 - 一个有效机会1 - 一个无效机会2 - 一个有效机会3 - 测试getById(1) 能正常查询 并且页面上需要展示的字段都有 - 测试getById(2) asertNull() 然后后面就是按部就班的开发了 我是从上往下进行开发的(或者说是从始到终进行开发的) 即先开发Service 再开发Mapper 开发完一个打个勾 - - 调用机会Service 获取基本信息 ✅ Mapper这一层的测试 直接连到是一个测试库 回滚依赖的是Spring自带的回滚机制 假设实际代码中只需查询 无需插入 那么会专门在该测试类中新建一个额外的Mapper 用于插入测试数据 如 class FooMapperTest { @Before public void setUp(){ testMapper.insert(1, 1, "name1"); } interface FooTestMapper{ @Insert("...") void insert(int isvalid, int id, String name, ...) } } service controller级别的测试 通过Mock的方式来测试 现在觉得是不是测的有点过于细了 比如 返回的行业 数据库中是逗号分割的字符串 返回给前端是一个行业数组 连VO中的的getIndustries()都测试了 assertNull(vo.getIndustries()) asserttArrayEquals(new String[]{"行业一","行业二"}, vo.getIndustries())
作者回复: 很赞的分享! 严格地说,还不够细,逗号分隔的字符串解析也应该拆出来。 程序员越舍不得在前期花时间,就越要在后期花时间。
2019-03-09322 - 行与修本节课我有以下几点体会: 1、从开发者的视角看编码和测试是不分家的,是可以通过重构形成良性生态圈的,类似之前课程中的反馈模型和红绿重构模型; 2、A-TRIP是个很好的总结和行动指南,在今后工作中应一以贯之,把工作做到扎实有成效; 3、对文中提到的数据库依赖的问题,我也说说自己的浅见。我觉得在测试代码中尽量避免与数据库打交道,测试更关注领域与业务,往往爆雷更多的是resource和service,模型的变化往往牵动着表结构的变化,与其两头兼顾不如多聚焦模型,我常用的做法是用例配合若干小文件(数据忠实于模型),保证库操作临门一脚前所有环节都是正确的,同时方便适应变化。一旦出现异常,也比较容易定位是否是数据库操作引发的问题。 (此点基于工作中发现项目型程序员大多是先急于把表结构定义出来,好像不这么做写代码就不踏实)
作者回复: 很好的总结!
2019-02-04415 - 俊伟以前我一直觉得先开发完,再写测试。而现在,通过专栏学习让我明白了,要去站在测试的角度去写代码。首先写测试,然后再想办法去实现逻辑。写代码的时候要时刻记住"我的代码应该怎么写才可以通过测试"。 其次测试还要写的尽可能简单,一个测试只测试一个功能。测试还不能依赖外部的环境,测试可以重复运行,而结果要保持一致。测试也是也要符合代码的规范。测试还要确保覆盖所有情况,不能出现无断言的测试。
作者回复: 其实,测试驱动开发才是最好的以终为始案例。
2019-02-1114 - 捞鱼的搬砖奇测试不仅是测试人员的工作。更是开发人员的工作。之前的工作的中自测,常常潜意识的里只会考虑正常的情况,比如输入姓名的input,只会输入不超过三个字符的长度,到测试手冲,会输入一长串,因为程序中没有做长度检查,超过数据库字段长度成都就挂了。后来自己总结,发现测试人员的测试会带着破坏的性质,开发人员总是认为一切操作都是合理的。 看完了文章后,会继续完善之前的总结。把什么场景可能出现什么情况,罗列出来,方便工作中的对照检查。
作者回复: 程序员要学点测试知识,比如,测试等价类的划分,破坏性测试等等,当你开始重视测试了,代码质量才会提高。
2019-02-04210 - 蓝士钦单元测试不好写是因为代码本身耦合度太高不好测试,应该拆分成更小的可测试单元,避免出问题时在一个大方法内靠场景复现人肉debug,拆分耦合的代码本身需要一定的分析设计能力,尽量遵循SOLID原则。 修改某块没有单元测试的旧业务代码时应该提取并补上单元测试,证明自己的修改没有问题。保证后期能够依靠单元测试放心大胆的无脑修改复杂的业务逻辑。每次修改业务都小心翼翼的在头脑中debug运行一次效率是最低的,人是最不可靠的,应该靠单元测试覆盖各种边界条件。 最佳实践Test Pyramid证明研发自身做好单元测试和基于UI的自动化测试相比更加重要,写完代码应该自动验证。
作者回复: 这个理解非常好!
2020-07-1929 - 钢之镇魂曲我是游戏服务器开发程序员,我经历过不少公司,但是从来没见过写测试的。不知道是不是游戏有什么特殊性?还是其他的什么问题?
作者回复: 没有特殊性,不写是一种现象,不是必然。当然,如果你问起,通常会有两类答案,没时间和我特殊。
2019-03-2329 - 旭老师春节快乐~ 开发和测试更像是矛盾的双方,对立但统一。之前做开发感觉测试影响了开发的效率,没事找事;后来接触测试感觉开发太过功利,只为实现而实现,实现不等于可用。矛与盾,同时在手,或许才能更好的战斗。
作者回复: 所以,要扩大自己的上下文。:)
2019-02-0428 - williamcai原来一直以为开发之后,手动测试一下功能就ok了,原来开发之前把测试写好是多么的难
作者回复: 所谓的难,实际上是练习少。
2019-02-1825 - rocedua trip 来自pragmatic unit test书,Java 版本《Pragmatic Unit Testing in Java8 wiht Junit》测试什么有个[Right]-BICEP的缩写,其中边界测试有个CORRECT的缩写,大家可以参考一下。 Right – Are the results right? 结果是否正确? B – are all the boundary conditions correct? 所有边界条件都是正确的么? I – can you check the inverse relationships? 能否检查一下反向关联? C – can you cross-check results using other means? 能够使用其他手段交叉检查一下结果? E – can you force error conditions to happen? 是否可以强制错误条件产生? P – are performance characteristics within bounds? 是否满足性能要求? CORRECT: Conformance(一致性):值是否和预期一致。可以理解为当输入并不是预期的标准数据时,被测试方法是否可以正确输出预期结果(或抛出异常)。 Ordering(顺序性):值是否像应该的那样是无序或有序的。 Range(区间性):值是否位于合理的最小值和最大值之间。 Reference(依赖性):代码是否引用了一些不在代码本身控制范围之内的外部资源,当这些外部资源存在或不存在、满足或不满足时,代码是否可以产生相应的预期结果。 Existence(存在性):值是否存在(是否为null、0、在一个集合中)。测试方法是否可以处理值不存在的情况。 Cardinatity(基数性):是否恰好有足够的值。这里的基数指的是计数,测试方法是否可以正确计数,并检查最后的计数值。 Time(相对或绝对时间性):所有事情的发生是否是有序的、是否在正确的时刻、是否恰好及时。与时间相关问题有:相对时间(时间上的顺序)、绝对时间(消耗的时间和钟表上的时间)、并发问题。例如:方法调用的时间顺序、代码超时、不同的本地时间、多线程同步等。
作者回复: 感谢补充的信息
2021-01-304
收起评论