程序员眼中的测试

码农的产品和服务大都是以软件形式存在的,我们存在的价值之一就是快速提供高质量的软件产品或服务。如何保障软件的高质量呢?这与软件测试分不开的,测试是保证软件质量的关键环节之一。

老码农早年曾经做过两年的软件测试(详见三本书影响一个人),现斗胆介绍一下老码农眼中的测试。

什么是软件测试?

软件测试是以评价一个程序或者系统属性为目标的任何一种活动。测试是对软件质量的度量。——《软件测试完全指南》 

远在1983年,IEEE对软件测试是:使用人工或自动的手段来运行或测定某个软件系统的过程,其目的在于检验它是否满足规定的需求或弄清预期结果与实际结果之间的差别。也就是说,软件测试的最初目的是为了检验软件系统是否满足需求。

虽然测试是为了发现程序中的错误而执行程序的过程,但并不仅仅是为了找出错误。通过分析错误产生的原因和发生趋势,还可以帮助我们发现当前软件开发过程中的缺陷,以便及时改进。当然,没有发现错误的测试也是有价值的,完整的测试是评定软件质量的一种方法。

简单地说,软件测试就是证明软件不存在错误的过程。

测试与QA的区别

QA是quality assurance的缩写,也就是质量保证。软件测试只是QA的一部分,是QA 的子集。QA的内容比测试大多了,能对产品、工作流程、组织方式等跟公司经营有关的事情进行反馈,很多外企的高层都是QA出身,尤其在制造业。 简单的来说,测试从技术上保证软件质量,QA从过程上保证软件质量。

QA关注的重点不仅仅是软件的质量,而是整个软件过程,尤其是过程和体系,例如ISO 9000系列的质量体系等。一句话,所有和质量相关的事都是QA的事。

测试的领域

刚入行的时候,从硬件工程师转作测试。严格遵循贝尔北方实验室的软件工程流程,将测试领域分为13个,由于年代的久远,现在只能记得11个了。

功能性测试 functionaliy

软件的功能性是第一要务,完成一半功能的成品也要强于完成了全部功能的半成品。 软件实现了哪些功能?是否真正完成了这些功能呢?

健壮性测试 Robustness

健壮性是指软件的容错能力,违约的输入能否导致故障的引入呢?长时间的压测是否会导致程序异常呢?

性能测试 performance

用户体验至上的背后是性能至上,良好的运行性能才能满足用户的预期。性能测试是和时间赛跑,测试软件的运行速度, 以及资源的使用率。

互操作性测试 interoperability

很多软件不是孤立存在的,不能因为用户使用了我们的软件,导致用户所使用的其他软件不能正常使用,同时还要保证用户正在使用的软件不会对我们的软件产出不良影响。尤其注意的是,软件间存在相互调用的情况。

辅助性测试 Accessibility test

可用性主要针对不同用户和不同场景。例如,前些天“饿了么”听障骑手的故事就说明了这个问题, 可用性测试没有过关,好在后来雪峰说新版本可以让听障骑手方便使用了。

易用性测试 usability

方便用户的使用又是一个比较泛的领域。用户的使用习惯,单双手操作,按钮的位置,实现目标功能的路径深度等等,都是易用性的考量指标,有时也会把审美考虑进去。

安全性测试 security

和健康一样,只有失去它的时候才知道它的可贵。数据源的安全,传输信道的安全,数据存储的安全等等,尽管安全的重要性众所周知,但真正为安全投入的公司并不是很多。

恢复性测试 recovery

恢复性测试一般指系统在正常或异常退出后,是否能够恢复到之前的运行状态。例如,很多手机都有recovery模式, 拔电源等破坏性测试有时也作为测试手段。

兼容性测试 compatibility

兼容性涉及的领域也较多。常见的如多浏览器测试,app的多机型测试等等,好在现在有一些云测试平台可以帮我们解决环境的兼容性问题。 我们更多的关注软件自身的不同版本间的兼容性,包括前向兼容和后向兼容。

发布测试 deliverable

发布测试更多是在软件的下载,安装,卸载等。一般地,把升级和热更新等也放到发布测试中,包括灰度升级。

文档完整性测试 documentation

文档是一个老话题,程序员经常抱怨文档不足,又往往讨厌写文档,陷入自相矛盾中。测试文档的完整性会被认为不那么重要,好的方式可能是从产品侧解决,让产品自身具有自解释性,代码也是如此。

测试的过程

测试的领域是从空间的维度对测试进行的分类,从时间的维度来看,又可以大概分为4个过程。

单元测试

单元测试:是在软件开发过程中要进行的最低级别的测试活动,在单元测试活动中,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。通常情况下,单元测试(模块测试)是RD编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。

集成测试

集成测试也叫组装测试或联合测试。在单元测试的基础上,将所有函数或程序模块按照设计要求(如根据结构图)组装成为子系统或系统,进行集成测试。通常情况下,集成测试是RD进行的一种检验程序内部各函数或各模块联合起来是否存在问题的一种方式。

端到端测试

端到端测试(E2E),其实就是对多个系统进行系统测试。在端到端测试中,业务流程是最重要的,端到端测试是范围最广的测试。集成测试主要关注系统之间的接口。系统之间需要交换信息,所以集成测试是端到端测试的先决条件。E2E的测试并不局限于功能性。在端到端的测试环境中,需要对服务的许多非功能性属性进行评估,如性能和安全性。

用户场景测试

用户场景测试是指部分真实用户对产品或服务的真实使用测试,在过去的电信类产品中是现场测试(field trial),或者beta测试。 对互联网产品往往是友好用户测试(公测),或者灰度升级测试。

基于阶段目标的测试

至于大家常说的黑白灰盒测试,是从产品细节的透明度来看的,程序员可以不必仔细区分。但是,对一些特定阶段的测试还需给予关注。

冒烟测试 smoke test

冒烟测试是在将代码更改签入到产品的发布版之前对这些更改进行验证的过程。在检查了代码后,冒烟测试是确定和修复软件缺陷的最经济有效的方法。冒烟测试用于确认代码中的更改会按预期运行,且不会破坏整个版本的稳定性。简单地说,冒烟测试是对软件基本的功能进行测试,以确认基本功能正常,保证系统能跑起来,正是进入测试阶段的版本必须首先通过冒烟测试的考验。

回归测试 regression test

回归测试是指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误。回归测试在整个软件测试过程中占有很大的工作量比重,开发的各个阶段都会进行多次回归测试。在渐进和快速迭代开发中,新版本的连续发布使回归测试进行的更加频繁,而在极限编程等敏捷流程中,更是要求每天都进行若干次回归测试。因此,选择正确的回归测试策略来改进回归测试的效率和有效性是非常有意义的。

压力测试 stress test

压力测试往往被安排在测试流程中相对靠后的阶段,是性能测试和健壮性测试的一部分。压测通过确定一个系统的瓶颈或者不能接受的性能点,来确定产品或服务可以正常服务的最高性能。通过测试系统在资源超负荷情况下的表现,或者在系统资源特别低的情况下软件系统运行情况,找到系统在哪里失效以及如何失效的地方。

其他针对专项的测试还有很多,例如面向配置的容量测试,面向安全的渗透性测试等等。

自动化测试

软件研发敏捷性的一个重要表征就是产品的自动化测试程度。自动化测试一般是使用自动化测试工具来进行的测试,不需要人为的干预。然而,自动化测试不是把手工测试从测试过程中抛弃,也不是要用自动化测试替代掉所有的手工测试。

自动化测试的前提是有充分的测试设计和数据准备,其中测试覆盖度完全是由测试设计来决定的,这就是测试架构师非常难得的原因之一。个人认为,自动测试更适合于相对稳定的功能测试,压力测试,尤其是各种回归测试。

客户端自动化测试工具一瞥

很多客户端平台自带了一些测试工具,例如Android 上的Monkey等,可以编写脚步利用这些工具

Web 测试: Selenium

Selenium 是一组跨平台的web应用自动化测试工具。通过使用Selenium,开发人员在不需要学习任何测试脚本语言的情况下,可以很容易地使用记录/回放测试工具来编写测试,让我想起了久远的MS-Test。

Selenium 提供对众多编程语言的支持,包括c#、Java、Groovy、Perl、PHP、Python、Ruby和各种流行的测试框架。

APP 测试:Appium

Appium是一个开源、跨平台的测试框架,可以用来测试原生及混合的移动端应用。Appium支持IOS、Android及FirefoxOS平台,使用WebDriver的json wire协议,来驱动iOS系统的UIAutomation库和Android系统的UIAutomator框架。

Appium支持Selenium WebDriver支持的所有语言,如java、Object-C、JavaScript、Php、Python、Ruby、C#、Clojure等,也可以使用Selenium WebDriver的Api。Appium支持任何一种测试框架,支持真正的跨平台自动化测试。

Appium采用C/S架构,客户端对webdriver做了封装,读取各种语言编写的测试脚本并转换为测试命令发送给服务端。服务端的两个功能,一是接收从Appium Client发送过来的命令(也就是测试用例),另一个是作为bootstrap客户端,接收client的命令后,通过socket方式,发给目标android机器的bootstrap,驱动Uiautomator执行自动化操作。

类似的还有rebotium 等。

服务端的自动化测试工具一瞥

服务端测试包括两部分:一种是针对web或app的服务端进行测试;另一种是针对后端的数据库,缓存系统,中间件或文件系统等进行的测试, 自动化工具不胜枚举。

请求模拟:postman

postman是Chrome的一个插件,从字面意思理解就是能够发送POST请求的工具,是一个非常卓越的WebAPI接口测试的工具,能够非常方便的构造Web请求并且验证返回的结果信息。如果用浏览器直接请求查看接口返回结果的话,修改参数以及发送post请求时很不方便,postman就提供了这些便利。

Postman请求支持多种格式解析如JSON/XML/文本,支持管理请求包括分组、重命名等,支持导出数据包存为文件或者云存储,而且是跨平台的,通过api 编程接口可以实现基于postman 的自动化测试。

抓包分析:charles

Charles 常用的网络抓包工具,通过将自己设置成系统的网络访问代理服务器,使得所有的网络访问请求都通过它来完成,从而实现了网络包的截取和分析,配合 SSL 功能,还可以分析Https协议。Charles 支持重发网络请求,修改网络请求参数,支持网络请求的截获并动态修改,更重要的是支持模拟慢速网络。

当然,也可以使用其他sniffer 工具配合wireshark 完成传输链路的自动化测试。

压力测试:AB

压测的工具很多,ab、http_load、webbench、siege等等。ab是apache自带的压力测试工具,是apachebench命令的简称。它非常实用,它不仅可对apache服务器进行访问压测,也可以对其它类型的服务器进行压测,比如nginx、tomcat等。

ab命令会创建多个并发访问线程,模拟多个访问者同时对某一URL地址进行访问。ab命令对发出负载的计算机要求很低,它既不会占用很高CPU,也不会占用很多内存,却会给目标服务器造成巨大的负载,其原理类似CC攻击。使用时需要注意的是,在刚开始压测的时候,负载不要太大,否则可能造成目标服务器资源耗完,严重时甚至导致死机。

对应更加完备的压测,可以使用LoadRunner 等其他商业工具软件。

面向测试的开发

对于程序员来讲,测试是保证高质量软件的关键手段之一。将质量思维融入开发流程,可以采用测试驱动开发(TDD)的极限编程方法,从业务入手,以测试先行的方法来反向推动代码的实现。

简单的说,就是每当需要添加一个新功能,或修改现有功能时,首先思考这部分代码期望达到的输入与输出,先把验证该业务的单元测试用例写出来,再去写最简单的实现代码来通过该测试;不断重复此过程直到完成整个功能。

典型的TDD开发步骤如下:

1. 分析并确定一个目标场景

  1. 用一个单元测试来验证该场景的输入输出

    3. 运行该测试,得到失败的测试结果

  2. 以最简单的功能代码来通过该测试

    5. 再次运行该测试,测试通过

  3. 进行代码重构,包括功能代码和单元测试代码

    7. 重复以上步骤,直至开发完成

在TDD中遵循一切从简的原则,以业务为导向,隔离目标场景,通过重构改进代码的可读性,可维护性,减少冗余代码等。同时维护一个测试列表 - 在开始开发之前,先列出所有需要的测试,并在开发中不断维护该列表,避免遗忘一些必要的测试。提高效率,不需要另外单独的文档,而是在测试类中对每个测试方法对应的业务场景,输入和期望的输出进行详细的描述。

由于测试先行,并达到足够的覆盖率,确保代码都经过了测试,有利提高代码质量。 TDD产生的测试就是对系统的一套完整的说明文档, 还能够产生一组完备的测试套件,这让团队可以更加自信得去进行代码重构。同时大大提高回归测试的频率,同时减少所花费的时间,避免产生冗余的,没有用的代码,减少对代码 Debugging 的时间。 将一个功能分解为一个个可以测试的更小单元,能够产生更小的,更清晰的,更加责任明确的类,更加松耦合的组件和清晰的接口。

ATDD是TDD的变种,TDD是基于单元测试的,而ATDD面向用户验收测试的。 在准备实施一个功能前,首先定义出期望的质量标准和验收细则,以及明确且达成共识的验收测试计划,以此来驱动开发人员的TDD实践和测试人员的测试脚本开发。对开发团队来说,ATDD 是由外向内,多方介入的,基于拉动策略的,并行开发测试方法;确保所有交付的产品都经过了充分的测试。

另外,BDD是TDD的补充,更适合高级别的业务需求和验收标准。通过用户故事定义需求,BDD定义的用户故事可以作为开发过程中的统一标准,促进开发人员、测试人员及用户共同协作。Cucumber是一个BDD自动化测试框架,提供了对自然语言定义行为及步骤的支持。在执行用例时,会通过行为和步骤定义自动调用步骤定义内的代码运行。同时,提供了良好的断言机制,当执行失败时,可以清晰的看到测试用例的执行步骤,明确失败原因。

事情都有两面性,没有银弹。TDD产生的代码质量取决于测试的质量,不正确的测试会产生错误的代码,业务场景覆盖不充分的测试液会产生功能不完整的代码。更重要的是,TDD只适用于输入输出明确的开发项目,不适用于某些探索性的,输出不确定的开发,比如人工智能,安全等领域的研发。 另外在某些环境,TDD实施会有一些困难,例如异步通信等,需要一些额外的辅助工具,增加了复杂性。

也就是说,TDD不是万能的,不可能完全依赖TDD来提高质量。它既无法替代集成测试、性能测试等,也不能让程序没有bug。关键一点,TDD不适合所有项目,要求需求必须足够清晰,对模型和依赖特别复杂的项目也不太行。

小结

No test, No quality!质量很多时候是产品存亡的关键因素,没有质量的产品很难说什么用户体验。作为一个程序员,要把质量思维融入到开发过程中,对测试做到胸中有数。

注: 本文所有桥的图片来自 中 国 古 桥!美醉了!一文。

附: 关于《深入分布式缓存》一书的签名赠送已经结束,详情参见昔我往矣 2017 一文的留言。

1