近日,极狐(GitLab) 江狐会第十四期在北京圆满落幕。
会上,途游游戏运维安全部研发负责人刘勇基于使用极狐GitLab 提高单元测试 ROI 的实践与体会,进行了《途游游戏敏捷开发工程实践》主题分享,为线上线下众多云计算用户、企业 IT 和运维工程师、架构师、开发者,以及开源和 DevOps 的爱好者们提供一些参考。
本文整理自途游游戏刘勇分享的核心内容,欢迎在公众号【极狐GitLab】首页消息对话栏回复 “途游” 获取 PPT,enjoy~
单元测试在敏捷开发流程中有什么意义?
单元测试是指对软件中的最小可测试单元进行检查和验证,一个单元测试就是一段自动化代码,这段代码调用被测试的目标单元,检查目标单元的行为是否符合开发人员的预期。
如图 1 所示,单元测试处于测试金字塔的最底层,也就是软件研发的早期阶段,属于白盒测试,是开发的组成部分。
图1
那么,单元测试在敏捷开发流程中有什么意义呢?
从研发质量角度考虑
敏捷的核心即拥抱变化,但变化带来风险。无论是因为重构、需求变更或其他导致代码必须要变更的时候,单元测试可以第一时间发现变更的代码是否会对业务逻辑造成破坏性的影响,这是单元测试最大的价值——守护程序的业务逻辑。
第二,“Talk is cheap,show me the code”,单元测试为研发人员提供了被测试代码的功能和使用案例,相当于更详细的文档,能够对改善代码结构产生积极的影响。
第三,有了高质量的单元测试,开发人员可以对已有代码进行有信心的变更,不管这种变更来自于业务和需求的变化,还是来自于重构。
最后,任何看起来难以测试的代码也将难以维护、发展,并且在其整个生命周期中都会受到许多错误的影响。因此,单元测试促使开发人员重新思考他们的编码方式,提升编码质量。
从测试的经济学角度考虑
如图 2 所示,85% 的 Bug 在 Coding 阶段产生,而传统的测试人员往往集中在 Function Test(功能测试)、System Test(系统集成测试)阶段,这些阶段修复 Bug 的成本数十倍增加,发布后的修复成本达到惊人数百倍。
图2
Bug 发现的越早,修复成本就越低。后期 Bug 的修复不仅增加沟通时间,还可能引入新的问题,增加测试验证时间,项目的进度也有延迟上线的风险。
因此,我们要尽可能地把测试左移,在软件开发的早期阶段通过单元测试发现 Bug ,更低成本处理 Bug ,提高代码质量,优化测试过程的投资回报率。
图3
总而言之,单元测试杠杠好!
那么如何实施单元测试?
基于极狐GitLab 的单元测试四步法
途游游戏使用极狐GitLab 进行软件研发实践,刘勇有一个深刻体会:极狐GitLab 能够很好地帮助你将花费很多精力、时间和成本,好不容易写出的单元测试的价值充分挖掘出来。
图4
首先回答一个问题:单元测试放在哪里?
可以放在单独目录里,如Java,Maven tests 目录、Package 和被测代码在一起;也可以和源代码在一起,如 Golang。原则上要尽可能离源码近。
如何实施单元测试?刘勇归纳了单元测试四步骤(AAAC):
- Arrange 筹备:为测试做准备;
- Act 执行:给予特定行为所需的上下文和输入并执行;
- Assert 断言:判断结果是否符合预期;
- Clear 清理环境:为后续测试保证上下文干净,测试之间彼此隔离没有依赖性。
1. Arrange 筹备:编写单元测试
Arrange 阶段就像多米诺骨牌之前的排列工作,为了接下来的行为可以被激发,包括但不限于准备所需的输入(对象、基础数据结构等)、启动/终止某服务如 MQ 或数据库、将一些数据预先存入数据库,为尚不存在的用户生成一些凭据等的事情。
极狐GitLab 有一个功能就是可以在容器里边提供很多预置的 Service,让数据库也好, MQ 也好,都可以很轻松地预先准备好。这样的话,当你在测试一个函数的时候,它的环境、数据库都会自动在你的 CI 环境中就绪,大幅提升单元测试的效率,是很贴心的服务。
单元测试准则参考
- 每个测试范围职责单一(不要试图用一个测试函数测试10个功能点)
- 执行速度快(总体执行时间不超过5分钟,单个用例执行时间3秒以内,TDD闭环时间)
- 自动化测试思路,避免人工查看,比如用 assert 代替 print 信息人眼识别结果
- 良好设计并命名的可复用单元测试体系
- 测试可以很容易的在 CI 流水线中在程序员的开发机上运行,不需要搭建或者很容易自动化搭建所需的测试环境
- 单元测试执行结果稳定,一个 10 次执行,8 次通过 2 次失败的单元测试是错误的
- 每一个测试函数所依赖的单元测试的环境应该是固定并干净的,测试函数彼此隔离,一个测试函数所依赖的环境不能是另一个测试函数执行后的结果
2. Act 执行:应用单元测试
Act 阶段对应多米诺骨牌,即是指尖轻轻推动第一块触发牌。费了很多的时间和精力,终于写完了单元测试,也能跑了,这个时候我们怎么去用它呢?
首先来看极狐GitLab 上的工作流(如图 4),途游游戏在此基础上稍加改动:把代码 Commit 之后,Push 到我们远端的服务仓库时会进行一次单元测试执行,在合并的时候会进行第二次次单元测试执行,在合并之后还会进行第三次单元测试执行。
图5
我们如何控制单元测试在何时执行?途游游戏在极狐GitLab上有两种触发方式,分别是:
(1)代码合并到主分支之前,每次代码 Push 到 MR 源分支,并基于合并后的代码(极狐Gitlab Merge Request Pipeline + Merge Result Pipelines)
首先,我们有一个主分支,往往是 Main 分支。这时候,如果我们要开发一个新功能,可根据这个新功能创建一个 Feature Branch 即特性分支,程序员会工作在特性分支上。
接着,我们会不断 Commit 代码,并且 Push 到在极狐GitLab 上创建的特性分支上,这个时候就会触发单元测试执行。
一般的 CI 工具只会执行在 Source Branch 上,就是源分支上,但极狐GitLab 有一个功能叫做 Merge Result Pipeline ,这是一个独特的功能,它的单元测试是跑在特性分支和组分支合并之后的代码上,而不需要你进行真正的合并,避免在特性分支的开发过程中,有其他人员抢先一步,在主分支上合并了其他的代码,而导致你的代码合并失败。
这也是途游游戏所秉承的 “测试前置”,即应用极狐GitLab 的 Merge Request Pipeline + Merge Result Pipeline 在特性分支上就发现问题,尽早解决问题。
(2)代码合并到主分支之后(CI/CD之前)
此时,途游游戏再次执行单元测试来守护代码,这里用到了极狐GitLab 的另一个功能:Job。一个抽象 Job 相当于 “类”,本身不会实例化,也不会真正地执行。做法是在前面加一个 “.”。
这样做的根本目标在于,需要在两种情况下执行同一个 Job,也就是 .unittest 这个 Job。这两种触发条件为:
- 有新的代码被提交到某一个 Merge Request 的源分支上
- 代码被合并到默认分支后
图6
3. Assert 断言:查看单元测试结果
单元测试的结果包括了测试覆盖率,以及若测试失败,其失败的结果。具体不同的语言,不同的测试框架,有不同的实现方式。
以 pytest 实现方式为例(如图 6),后面四行就是该单元测试的报告,用来衡量单元测试成功与否;前缀是与测试覆盖率相关的一些东西,在极狐GitLab 界面上可以看到这个结果,实现单元测试用例执行结果的可视化。
pytest itom_cmdb_tests/
--create-db
--migrations
--vcr-record none
--junitxml=test_reports/itom_cmdb_testcase_report.xml
--cov-report xml:test_reports/coverage.xml
--cov-report term
--cov=itom_cmdb itom_cmdb_tests/tests/
图7 展示的是极狐GitLab 的 Merge Request 的 Code Review 视图:绿色杠杆就是有单元测试的一些代码,红色是单元测试没有覆盖到的代码。极狐GitLab 会自动获取相应信息,并且上传到极狐GitLab 的环境当中。
图7
4. Clear 清理:清除单元测试环境
在单元测试最后阶段,需要清理测试环境,例如还原全局配置、清理创建的文件目录等,为后续测试保留干净的上下文,确保测试之间彼此隔离没有依赖性。
以上就是途游游戏运维安全部研发负责人刘勇分享的单元测试主要内容,欢迎关注极狐GitLab 持续获取各类技术干货!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。