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