10x 程序员工作法
郑晔
开源项目 Moco 作者
53432 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 63 讲
思考框架 (1讲)
10x 程序员工作法
15
15
1.0x
00:00/00:00
登录|注册

16 | 为什么你的测试不够好?

测试应该简单
编写可测试的代码
Professional(专业的)
Independent(独立的)
Repeatable(可重复的)
Thorough(全面)
Automatic(自动化)
复杂的测试
没有断言
执行部分过于复杂
清理
断言
执行
前置准备
写好测试的关键
A-TRIP
测试的坏味道
测试的结构
测试的重要性
测试

该思维导图由 AI 生成,仅供参考

你好!我是郑晔。今天是除夕,我在这里给大家拜年了,祝大家在新的一年里,开发越做越顺利!
关于测试,我们前面讲了很多,比如:开发者应该写测试;要写可测的代码;要想做好 TDD,先要做好任务分解,我还带你进行了实战操作,完整地分解了一个任务。
但有一个关于测试的重要话题,我们始终还没聊,那就是测试应该写成什么样。今天我就来说说怎么把测试写好。
你或许会说,这很简单啊,前面不都讲过了吗?不就是用测试框架写代码吗?其实,理论上来说,还真应该就是这么简单,但现实情况却往往相反。我看到过很多团队在测试上出现过各种各样的问题,比如:
测试不稳定,这次能过,下次过不了;
有时候是一个测试要测的东西很简单,测试周边的依赖很多,搭建环境就需要很长的时间;
这个测试要运行,必须等到另外一个测试运行结束;
……
如果你也在工作中遇到过类似的问题,那你理解的写测试和我理解的写测试可能不是一回事,那问题出在哪呢?
为什么你的测试不够好呢?
主要是因为这些测试不够简单。只有将复杂的测试拆分成简单的测试,测试才有可能做好。

简单的测试

测试为什么要简单呢?有一个很有趣的逻辑,不知道你想没想过,测试的作用是什么?显然,它是用来保证代码的正确性。随之而来的一个问题是,谁来保证测试的正确性?
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文从测试的简单性出发,探讨了测试的写作方式。作者指出,简单的测试是好测试的基础,而一个简单的测试应该包括前置准备、执行、断言和清理四个部分。此外,文章还提到了常见的测试“坏味道”,如执行部分过于复杂、缺乏断言以及测试本身过于复杂等问题。作者强调了测试的简单性和清晰性对于保证代码正确性的重要性,鼓励读者多写简单的测试来覆盖不同的场景,而不是试图在一个测试中覆盖过多的功能。通过本文,读者可以了解到如何写出简单、清晰、有效的测试,以及避免常见的测试陷阱。文章还介绍了A-TRIP五个单词的缩写,分别是Automatic(自动化)、Thorough(全面)、Repeatable(可重复的)、Independent(独立的)和 Professional(专业的),这些特点是衡量好测试的标准。最后,作者强调了写好测试的关键在于写简单的测试,鼓励读者分享对测试的新理解。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《10x 程序员工作法》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(36)

  • 最新
  • 精选
  • 西西弗与卡夫卡
    可重复特别重要,有些开发在本地测和数据库相关应用时,由于前置依赖数据比较多,为了避免测试前写冗长的数据准备代码,所以会预先在数据库中准备好初始数据。每个测试再初始化特定的数据,因为Spring测试框架可以自动回滚,所以在本地是可以重复跑的。但是,放到CI中时,测试就统统没法过了,因为CI的数据库是共用的,没有本地的那份初始化数据集。一种方式是,保持数据库干净,用测试时用初始化脚本准备数据。如果测的场景比较复杂,比如要测多个事务的交互结果,还可以引入Docker,将依赖的数据库及初始化数据做成Docker的image,测试代码就更加简单,并且可以重复运行了,只要CI支持Docker即可

    作者回复: 很好的分享!

    2019-02-04
    5
    31
  • 朱国伟
    我还是习惯先写代码 在写测试 如 有一个投资机会详情(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-09
    3
    22
  • 行与修
    本节课我有以下几点体会: 1、从开发者的视角看编码和测试是不分家的,是可以通过重构形成良性生态圈的,类似之前课程中的反馈模型和红绿重构模型; 2、A-TRIP是个很好的总结和行动指南,在今后工作中应一以贯之,把工作做到扎实有成效; 3、对文中提到的数据库依赖的问题,我也说说自己的浅见。我觉得在测试代码中尽量避免与数据库打交道,测试更关注领域与业务,往往爆雷更多的是resource和service,模型的变化往往牵动着表结构的变化,与其两头兼顾不如多聚焦模型,我常用的做法是用例配合若干小文件(数据忠实于模型),保证库操作临门一脚前所有环节都是正确的,同时方便适应变化。一旦出现异常,也比较容易定位是否是数据库操作引发的问题。 (此点基于工作中发现项目型程序员大多是先急于把表结构定义出来,好像不这么做写代码就不踏实)

    作者回复: 很好的总结!

    2019-02-04
    4
    15
  • 俊伟
    以前我一直觉得先开发完,再写测试。而现在,通过专栏学习让我明白了,要去站在测试的角度去写代码。首先写测试,然后再想办法去实现逻辑。写代码的时候要时刻记住"我的代码应该怎么写才可以通过测试"。 其次测试还要写的尽可能简单,一个测试只测试一个功能。测试还不能依赖外部的环境,测试可以重复运行,而结果要保持一致。测试也是也要符合代码的规范。测试还要确保覆盖所有情况,不能出现无断言的测试。

    作者回复: 其实,测试驱动开发才是最好的以终为始案例。

    2019-02-11
    14
  • 捞鱼的搬砖奇
    测试不仅是测试人员的工作。更是开发人员的工作。之前的工作的中自测,常常潜意识的里只会考虑正常的情况,比如输入姓名的input,只会输入不超过三个字符的长度,到测试手冲,会输入一长串,因为程序中没有做长度检查,超过数据库字段长度成都就挂了。后来自己总结,发现测试人员的测试会带着破坏的性质,开发人员总是认为一切操作都是合理的。 看完了文章后,会继续完善之前的总结。把什么场景可能出现什么情况,罗列出来,方便工作中的对照检查。

    作者回复: 程序员要学点测试知识,比如,测试等价类的划分,破坏性测试等等,当你开始重视测试了,代码质量才会提高。

    2019-02-04
    2
    10
  • 蓝士钦
    单元测试不好写是因为代码本身耦合度太高不好测试,应该拆分成更小的可测试单元,避免出问题时在一个大方法内靠场景复现人肉debug,拆分耦合的代码本身需要一定的分析设计能力,尽量遵循SOLID原则。 修改某块没有单元测试的旧业务代码时应该提取并补上单元测试,证明自己的修改没有问题。保证后期能够依靠单元测试放心大胆的无脑修改复杂的业务逻辑。每次修改业务都小心翼翼的在头脑中debug运行一次效率是最低的,人是最不可靠的,应该靠单元测试覆盖各种边界条件。 最佳实践Test Pyramid证明研发自身做好单元测试和基于UI的自动化测试相比更加重要,写完代码应该自动验证。

    作者回复: 这个理解非常好!

    2020-07-19
    2
    9
  • 钢之镇魂曲
    我是游戏服务器开发程序员,我经历过不少公司,但是从来没见过写测试的。不知道是不是游戏有什么特殊性?还是其他的什么问题?

    作者回复: 没有特殊性,不写是一种现象,不是必然。当然,如果你问起,通常会有两类答案,没时间和我特殊。

    2019-03-23
    2
    9
  • 老师春节快乐~ 开发和测试更像是矛盾的双方,对立但统一。之前做开发感觉测试影响了开发的效率,没事找事;后来接触测试感觉开发太过功利,只为实现而实现,实现不等于可用。矛与盾,同时在手,或许才能更好的战斗。

    作者回复: 所以,要扩大自己的上下文。:)

    2019-02-04
    2
    8
  • williamcai
    原来一直以为开发之后,手动测试一下功能就ok了,原来开发之前把测试写好是多么的难

    作者回复: 所谓的难,实际上是练习少。

    2019-02-18
    2
    5
  • rocedu
    a 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-30
    4
收起评论
显示
设置
留言
36
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部