You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
16 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 29 | 自动化测试如何把Bug杀死在摇篮里
你好我是宝玉。前不久我所在项目组完成了一个大项目把一个网站前端的jQuery代码全部换成React代码涉及改动的项目源代码文件有一百多个变动的代码有几千行最终上线后出乎意料的稳定只有几个不算太严重的Bug。
能做到重构还这么稳定,是因为我们技术水平特别高吗?当然不是。还是同样一组人,一年前做一个比这还简单的项目,上线后却跟噩梦一样,频繁出各种问题,导致上线后不停打补丁,一段时间才逐步稳定下来。
这其中的差别,只是因为在那次失败的上线后,我们总结经验,逐步增加了自动化测试代码的覆盖率。等我们再做大的重构时,这些自动化测试代码就能帮助我们发现很多问题。
当我们确保每一个以前写好的测试用例能正常通过后就相当于把Bug杀死在摇篮里再配合少量的人工手动测试就可以保证上线后的系统是稳定的。
其实对于自动化测试,我们专栏已经多次提及,它是敏捷开发能快速迭代很重要的质量保障,是持续交付的基础前提。
所以今天我将带你一起了解什么是自动化测试,以及如何在项目中应用自动化测试。
## 为什么自动化测试能保障质量?
自动化测试并不难理解,你可以想想人是怎么做测试的:首先根据需求写成测试用例,设计好输入值和期望的输出,然后按照测试用例一个个操作,输入一些内容,做一些操作,观察是不是和期望的结果一致,一致就通过,不一致就不通过。
自动化测试,就是把这些操作,用程序脚本来完成的,本质上还是要输入和操作,要检查输出是不是和期望值一致。只要能按照测试用例操作和检查,其实是人来做还是程序来做,结果都是一样的。
不过,自动化测试有一个手工测试没有的优势,那就是可以直接绕过界面,对程序内部的类、函数进行直接测试,如果有一定量的自动化测试代码覆盖,相对来说软件质量是更有保障的。
而且一旦实现了自动化每测试一次的成本其实大幅降低了的几百个测试用例可能几分钟就跑完了。尤其是每次修改完代码合并到主干之前把这几百个测试用例跑一遍可以有效地预防“修复一个Bug而产生新Bug”的情况发生。
但现阶段,自动化测试还是不能完全代替手工测试的,有些测试,自动化测试成本比手工测试成本要高,比如说测试界面布局、颜色等,还是需要一定量的手工测试配合。
## 有哪些类型的自动化测试?
现在说到自动化测试已经有很多的概念除了大家熟悉的单元测试还有像集成测试、UI测试、端到端测试、契约测试、组件测试等。而很多时候同一个名字还有不同的解读很容易混淆。
在对自动化测试类型的定义方面Google的分类方法我觉得比较科学根据数据做出决策而不仅仅是依靠直觉或无法衡量和评估的内容。Google将自动化测试分成了三大类小型测试、中型测试和大型测试。
假设我们有一个网站是基于三层架构如下图所示业务逻辑层的类叫UserService类数据访问层的类叫UserDA我们将以用户注册的功能来说明几种测试的区别 。
![](https://static001.geekbang.org/resource/image/9b/2b/9b9fbf93cf03fa33b381ee144a26a92b.png)
#### 小型测试
小型测试是为了验证一个代码单元的功能,例如针对一个函数或者一个类的测试。我们平时说的单元测试就是一个典型的小型测试。
比如说UserService这个类有一个注册用户的函数现在要对它写一个单元测试代码那么看起来就像下面这样
![](https://static001.geekbang.org/resource/image/02/61/02aa850792c8fbb3c6bc626b9c944161.png)
通过这样的测试代码就可以清楚的知道UserService类的create这个函数是不是能正常工作。
小型测试的运行不需要依赖外部。如果有外部服务比如文件操作、网络服务、数据库等必须使用一个模拟的外部服务。比如上面例子中我们就使用了FakeUserDA这个模拟的数据库访问类实际上它不会访问真实的数据库。这样可以保证小型测试在很短时间内就可以完成。
![](https://static001.geekbang.org/resource/image/43/ee/43ce39715dddae2e51728d13714c31ee.png "小型测试图片来源《Google软件测试之道》")
#### 中型测试
中型测试是验证两个或多个模块应用之间的交互,通常也叫集成测试。
如果说要对用户注册的功能写集成测试那么就会同时测试业务逻辑层的UserService类和数据访问层的UserDA类。如下所示
![](https://static001.geekbang.org/resource/image/7d/e7/7dcb05ac985cdc8b554c9ba3b5691ee7.png)
对于中型测试,可以使用外部服务(比如文件操作、网络服务、数据库等),可以模拟也可以使用真实的服务。比如上面这个例子,就是真实的数据库访问类,但是用的内存数据库,这样可以提高性能,也可以减少依赖。
至于中型测试要不要使用模拟的服务,有个简单的标准,就是看能不能在单机情况下完成集成测试,如果可以就不需要模拟,如果不能,则要模拟避免外部依赖。
![](https://static001.geekbang.org/resource/image/cf/ab/cfb41a9f0a490a3e1aa54555e4d35eab.png "中型测试图片来源《Google软件测试之道》")
#### 大型测试
大型测试则是从较高的层次运行,把系统作为一个整体验证。会验证系统的一个或者所有子系统,从前端一直到后端数据存储。大型测试也叫系统测试或者端对端测试。
如果说要对用户注册写一个端对端测试的例子,那么看起来会像这样:
![](https://static001.geekbang.org/resource/image/73/b0/736f1fc4609ba5d5c408b243b30834b0.png)
对于大型测试通常会直接使用外部服务比如文件操作、网络服务、数据库等而不会去模拟。比如上面这个例子就是直接访问测试环境的地址通过测试库提供的API操作浏览器界面输入测试的用户名密码点击注册按钮最后检查输出的结果是不是符合预期。
![](https://static001.geekbang.org/resource/image/30/af/30ab7a154bc2324f1d4b858e36ad03af.png "大型测试图片来源《Google软件测试之道》")
#### 区分测试类型的依据是什么?
以上就是主要的自动测试类型了。捎带着补充一个测试类型,那就是契约测试,这个测试最近出现的频率比较高,主要是针对微服务的。其实就是让微服务在测试时,不需要依赖于引用的外部的微服务,在本地就可以模拟运行,同时又可以保证外部微服务的接口更新时,本地模拟的接口(契约)也能同步更新。对契约服务更多的说明可以参考这篇文章:《 [聊一聊契约测试](http://insights.thoughtworks.cn/about-contract-test/) 》
那么契约测试,属于大型测试还是中型测试呢?
Google针对这几种测试类型列了一张表根据数据给出了明确区分
[![](https://static001.geekbang.org/resource/image/a7/bd/a72fcd3b3f358e4512fa5694ad526dbd.png "图片来源Google Testing Blog")](http://testing.googleblog.com/2010/12/test-sizes.html)
结合上面的表格其实就很好区分了:
* 小型测试,没有外部服务的依赖,都是要模拟的;
* 中型测试,所有的测试几乎都不需要依赖其他服务器的资源,如果有涉及其他机器的服务,则本地模拟,这样本机就可以完成测试;
* 大型测试,几乎不模拟,直接访问相关的外部服务。
所以现在你应该就知道契约测试,也是中型测试的一种了,因为它不需要依赖外部服务,本机就可以完成测试。
为什么中型测试这么看重“能单机运行”这一点呢?因为这样才方便在持续集成上跑中型测试,不用担心外部服务不稳定而导致测试失败的问题。
上面的表中还反映出一个事实:**越是小型测试,执行速度越快,越是大型测试,执行速度越慢。**通常一个项目的小型测试,不超过一分钟就能全部跑完,一个中型测试,包括一些环境准备的时间,可能要几分钟甚至更久,而大型测试就更久了。
**另外越是大型测试,写起来的成本也相应的会更高,所以一般项目中,小型测试最多,中型测试次之,大型测试最少。**就像下面这张金字塔图一样。所以我们也常用测试金字塔来区分不同类型的测试粒度。
[![](https://static001.geekbang.org/resource/image/61/cf/616bb4cdb13884dde562b10568ba77cf.png "测试金字塔,图片来源: TestPyramid")](http://martinfowler.com/bliki/TestPyramid.html)
如果你对测试类型很感兴趣,可以参考《[测试金字塔实战](http://insights.thoughtworks.cn/practical-test-pyramid/)》这篇文章作为补充。
#### 怎么写好自动化测试代码?
很多人认为写自动化测试很复杂,其实测试代码其实写起来不难,包含四部分内容即可,也就是:准备、执行、断言和清理,我再把第一段代码示例贴一下:
![](https://static001.geekbang.org/resource/image/02/61/02aa850792c8fbb3c6bc626b9c944161.png)
第一步就是准备,例如创建实例,创建模拟对象;第二步就是执行要测试的方法,传入要测试的参数;第三步断言就是检查结果对不对,如果不对测试会失败;第四步还要对数据进行清理,这样不影响下一次测试。
上面还有几个测试代码示例,都是这样的四部分内容。
这是针对写一个自动化测试的代码结构。对于同一个功能,通常需要写几个自动化测试才完整。
一个完整的自动化测试要包括三个部分的测试:
* **验证功能是不是正确:**例如说输入正确的用户名和密码,要能正常注册账号;
* **覆盖边界条件:** 比如说如果用户名或密码为空,应该不允许注册成功;
* **异常和错误处理:**比如说使用一个已经用过的用户名,应该提示用户名被使用。
![](https://static001.geekbang.org/resource/image/05/a8/055004435b9bcd81cfa13050a8f42aa8.png)
所以你看,写一个测试代码并没有你想的那么复杂,那还有什么理由不去写测试呢?
## 如何为你的项目实施自动化测试?
现在你了解了有哪些类型的测试,如何写自动化测试代码,也许迫不及待想在项目中实施自动化测试。
#### 选择好自动化测试框架
要写好自动化测试代码,首先要找对自动测试化框架。不同的语言,不同的平台,测试的框架都不一样。好在现在搜索引擎很方便,根据“你的语言+自动测试框架”的关键字,就能找到很多的结果。这里我也帮你找了一些,供参考。
* Web前端
[Jest](http://github.com/facebook/jest) Facebook的前端测试框架
[Mocha](http://mochajs.org)历史悠久的一个JS测试框架
[Nighwatch](http://nightwatchjs.org): 一个API很简单但是功能很强大可以直接操作浏览器的自动测试框架。
* iOS开发
可以参考这篇文章《[iOS自动化测试框架对比](http://www.jianshu.com/p/047035416095)》。
* 安卓开发
可以参考这篇文章《[Android 谈谈自动化测试](http://juejin.im/entry/59ec4a8f6fb9a0450908a5fd)》。
#### 在持续集成环境上跑你的自动化测试
选好自动化测试框架后,你的自动化测试代码,其中的小型测试和中型测试,最好要能在持续集成环境上运行起来。
**让自动化测试在持续集成上运行非常重要,只有这样才能最大化地发挥自动化测试的作用。**
因为持续集成会强制测试通过才能合并代码在合并代码之前就能知道测试是不是都通过了可以帮助程序员获得最直观的反馈知道哪里可能存在问题这样才能真正做到防患于未然把Bug杀死在摇篮里。
下图描述的就是自动测试配合持续集成的一个标准流程:
* 在提交代码前,先本地跑一遍单元测试,这个过程很快的,失败了需要继续修改;
* 单元测试成功后就可以提交到源代码管理中心,提交后持续集成服务会自动运行完整的自动化测试,不仅包括小型测试,还有中型测试;
* 通过所有的测试后,就可以合并到主分支,如果失败,需要本地修改后再次提交,直到通过所有的测试为止。
[![](https://static001.geekbang.org/resource/image/7b/38/7bbc58d82864974ff2f9ec31347fa538.png "图片来源Microservice Testing: Unit Tests")](http://medium.com/@nathankpeck/microservice-testing-unit-tests-d795194fe14e)
#### 新项目和老项目的不同策略
如果是新项目那么可以在一开始就保持一定的自动化测试代码的覆盖率你甚至还可以试试测试驱动TDD的开发模式也就是先写测试代码再写实现代码保证测试通过最后对代码进行重构。
![](https://static001.geekbang.org/resource/image/67/66/67f2886f7dee6f24e5a833e6b4c94f66.png "图片来源:郑晔 《10x程序员工作法》专栏")
如果是老项目,短期内要让自动化测试代码有覆盖是有难度的,可以先把主要的功能场景的中型测试写起来,这样可以保证这些主要功能不会轻易出问题。
后面在维护的过程中:
* 增加新功能的时候,同步对新功能增加自动化测试代码;
* 修复Bug的时候针对Bug补写自动化测试代码。
这样一点一点,把自动化测试代码覆盖率加上去。
#### 如果时间紧任务重,来不及写自动化测试怎么办?
确实遇到时间紧的情况我建议你要优先保证中型测试代码的覆盖因为这样至少可以保证主要的用户使用场景是正常的。然后把来不及完成的部分创建一个Ticket放到任务跟踪系统里面后面补上。
## 总结
今天我带你一起学习了关于自动化测试有关的知识。自动化测试,分为三类:
* 小型测试,主要针对函数或者类进行验证,不调用外部服务,执行速度快;
* 中型测试,主要验证两个或多个模块应用之间的交互,可能会调用外部服务,尽可能让所有测试能在本机即可完成,执行速度比较快;
* 大型测试,对服务整体进行验证,执行速度慢。
写好单元测试代码,基本结构就是:准备、执行、断言和清理;基本原则就是:
* 要验证正确性;
* 覆盖边界条件;
* 验证是否有异常和错误的处理。
自动化测试,一定要配合好持续集成,才能最大化发挥其效用。
对于自动化测试的实施,开头是最难的,因为需要花时间选择自动化测试框架,需要针对自动化测试框架搭建环境,甚至要去搭建持续集成环境。但搭建持续集成和搭建自动化测试环境,并且保证持续更新维护自动测试代码,这个技术投资,一定是你在项目中最有价值的投资之一。
搭建持续集成环境和集成自动化测试框架的事情,要作为一个正式的项目任务去做,当作一个很重要的任务去推进。
## 课后思考
你所在项目中,自动化测试代码覆盖如何?保持高覆盖率的主要阻力或者障碍是什么?打算怎么改善项目中自动化测试代码的覆盖?欢迎在留言区与我分享讨论。
感谢阅读,如果你觉得这篇文章对你有一些启发,也欢迎把它分享给你的朋友。