软件测试的心理学和经济学
软件测试人员在测试过程中要有正确的态度(愿景)
心理学
软件测试的定义需要明确:软件测试的根本应该聚焦到为程序增加价值,让程序变得更加可靠,是找出问题并让问题得到解决的过程
测试是为了发现错误而执行程序的过程
一次“成功”的测试不是“没有发现错误”的测试,而应该是尽可能发现多的错误的的测试,因为只有发现了尽可能多的问题并且进行修复,才是真正意义上的为程序增加了可靠性,增加了价值
经济学
软件测试最可靠的方法肯定是“穷举法”,基于这种方法,我们可以将测试分为数据驱动的黑盒测试和逻辑驱动的白盒测试
无论是黑盒测试“数据穷举”,还是白盒测试的“逻辑路径穷举”,用例设计的数量都会是天文数字,所以是不可能实现的
测试面临的经济学挑战就是如何通过有限的测试用例,最大限度发现更多的问题,取得尽可能好的测试效果
软件测试的十条原则
-
测试用例中一个必须部分是对预期输出或结果进行定义
- 对程序的输入数据的描述
- 对程序在上述输入数据下的正确输出结果的精确描述
以上两点是为了通过明确的输入输出描述来避免人们”渴望看到正确结果“的倾向性,避免存在”想当然“等模棱两可的感性判断
-
程序员应该避免测试自己编写的程序
-
编写软件的组织不应该测试自己编写的软件
-
应该彻底检查每个测试的执行结果
-
测试用例的编写不仅应该根据有效和预料到的输入情况,也应该根据无效和未预料到的输入情况
-
检查程序是否“未做其应该做的”并不完整,也应该检查程序是否“做了其不应该做的”
-
避免测试用例用后即弃,除非软件是一次性的
-
计划测试工作的时候,不应该默许假设不会发现错误
-
程序某部分存在更多错误的可能性,与该部分已经发现错误的数量成正比
错误总是倾向于狙击存在,在一个具体的程序中,某些部分比其他部分更容易存在错误
-
软件测试是一项极富创造性、极具智力挑战的工作
人工测试——代码检查、走查与评审
错误发现得越早,改正错误的成本越低,正确改正错误的可能性越大
代码检查与走查
这种方法有两个优点,一是足够精确,代码检查与走查找出来的错误,可以直接精确定位,降低了调试成本,二是这个过程往往可以发现成批错误。
基于计算机的测试往往只能在运行过程才能逐个发现某一流程不通或者其他的表征错误
代码检查
以组为单位阅读代码,是一系列规程和错误检查技术的集合
检查小组
- 协调人:
- 分发代码检查所需材料,安排会议进程
- 主导整个代码检查过程
- 记录发现的所有错误
- 跟踪错误,确保错误得到改正
- 代码作者
- 程序的其他设计人员
- 测试专家——熟悉大部分常见编码错误、有较高软件测试造诣
检查议程
- 代码作者逐条陈述代码逻辑结构
- 参考常见编码错误列表分析程序
与人相关
只针对事情不要针对人,对检查结果保密,代码作者最好站在非自我本位的态度对待检查过程,所有的一切都是为了发现错误并解决错误,从而提高整个程序的可靠性即价值
代码检查其他好处
- 程序员可以得到编程风格、算法选择、编程技术等反馈
- 其他参与者可以通过接触错误和编程风格有所收获
- 代码检查可以在前期就发现程序的脆弱部位,降低了测试成本
常见代码错误列表
代码走查
与代码检查形式、过程大体相似,但是会基于一些简单的测试用例去进行推演,观察程序运行逻辑
桌面检查
担任进行的代码检查或代码走查,效率低下,过程无约束,不推荐
同行评审
依据程序整体质量、可维护性、可扩展性、易用性和清晰性对匿名程序进行评价的技术,目的是为程序员提供自我评价的手段
首先选出一名程序员作为管理员,接着管理员挑出6-20名参与者(背景相同,如使用同一种编程语言),每位参与者挑选两个自己编写的程序一共评审,其中为自评较好的作品,另一个为自评较差的作品
收集完将所有程序随机分发给参与者,每个人拿到4个,两”好“两”差“的程序,之后进行打分
- 程序是否易于理解?
- 高层次的设计是否可见且合理?
- 低层次的设计是否可见且合理?
- 修改此程序对评审者而言是否很容易?
- 评审者是否会以编写出此程序而感到自豪?
测试用例的设计
软件测试重要因素——设计和生成有效的测试用例
软件测试关键问题——在所有可能的测试用例中,哪个子集最有可能发现最多的错误?
黑盒测试+白盒测试结合来尽量设计出一个严格的测试
黑盒测试:
等价类划分
边界值分析
因果图分析
错误猜测
白盒测试:
语句覆盖
判定覆盖
条件覆盖
判定/条件覆盖
多重条件覆盖
白盒测试
白盒测试关注用例执行的程度或者覆盖程序逻辑结构(源代码)的程度
逻辑覆盖测试
语句测试有很大的不足之处,通常使用较少
判定覆盖(分支覆盖)是一种较强写的逻辑覆盖准则——编写足够多的测试用例,使得每一个判断都至少有一个为真和为假的输出结果
黑盒测试——数据驱动
数据(输入/输出)驱动的黑盒测试,基于程序规格说明书,目的是找出程序不符合规格说明书的地方
等价类划分
思想:穷举无法实现,因此测试被限制在从所有可能的输入中努力找出某个代表性的子集
因此这个确定的这个子集需要具备两种特性:
- 严格控制测试用例的增加
- 覆盖了大部分其他可能的测试用例
我的理解就是要更加具有代表性,因为有代表性所以数量有一定的限制,同时也可以为我们做出的一些假设提供了合理性
等价类划分法设计测试用例主要有两个步骤:(1)确定等价类;(2)生成测试用例
确定等价类
选取每一个输入条件,将其划分成两个或更多组
外部条件 | 有效等价类 | 无效等价类 |
---|---|---|
有效等价类:程序的有效输入
无效等价类:有效输入外的任何可能输入条件(包含无效和未预料到的输入)
确定等价类的一些指导原则:
- 输入条件规定取值范围,如1-999
- 确定1个有效等价类:1<x<999
- 确定两个无效等价类:x<1,x>999
- 输入条件规定取值个数,如:审批人1-6个人
- 确定1个有效等价类:1≤x≤6
- 确定两个无效等价类:x<1,x>6
- 输入条件规定一个输入值的集合:交通工具必须为:公共汽车、卡车、出租车
- 确定1个有效等价类:x∈[公共汽车,卡车,出租车]
- 确定1个无效等价类:x=摩托车
- 输入条件规定“必须是”:密码首位必须为字母
- 确定1个有效等价类:首位为字母
- 确定1个无效等价类:首位不为字母
生成测试用例
- 为每个等价类设置唯一编号
- 编写新用例尽可能多地覆盖尚未涵盖的有效等价类,直到所有有效等价类都被测试用例所覆盖
- 编写新的用例,覆盖一个且仅覆盖一个尚未被覆盖的无效等价类,直到所有无效等价类都被测试用例所覆盖
边界值分析
边界值分析是一项回报率很高的测试用例设计手段,与等价类划分相比较,主要有两个不同点:
- 等价类划分是根据输入条件划分等价类,从等价类中挑选任意一个元素作为代表进行测试;边界值分析需要选择一个或者多个元素,保证每个边界都经过一次测试
- 与仅仅关注输入条件不同,边界值分析还需要考虑从结果空间设计测试用例
边界值分析强调一定的“创造性”,下面给出一些简单的参考:
- 输入条件规定取值范围:-1.0~+1.0
- 针对-1.0设计用例
- 针对+1.0设计用例
- 针对-1.001和+1.001设计用例
- 输入条件规定了取值数量,针对以下几点设计测试用例
- 最小输入值
- 最大输入值
- 最小输入值-1
- 最大输入值+1
- 对每个输出条件应用上述第一点:例如扣款限额为0元~99.99元,那么应该根据具体程序的计算规则,反向计算出扣款额度为负数、0元和99.99元的输入条件,根据输入条件进行测试
- 对每个输出条件应用上述第二点:例如搜索结果列表最多显示4条信息,那么想办法去设计搜索结果为0、4、5的输入条件,根据输入条件进行测试
- 如果程序输入或者输出为一个有序序列,应该特别注意序列的第一个和最后一个元素
- 探索其他任何可能的边界条件
边界值分析是一个比较考验人的方法,尽管听起来很简单,但真正确定一个个边界条件仍然需要耗费一定的时间和精力
因果图
在引入因果图之前,我们先分析边界值分析和等价类划分的一个弱点:没有针对输入条件的组合进行分析。
因果图除了可以使用一个系统的方法选出高效的测试用例集,还可以根据罗列的条件组合指出规格说明的不完整性和不明确指出
根据因果图生成测试用例的步骤如下:
- 将需求原型分解为可执行片段
- 确定需求原型中的因果关系
- 因:一个明确的输入条件或输入条件的等价类
- 果:一个输出条件或者系统转换(输入对程序或者系统状态的延续影响)
- 分析需求原型中的予以内容,将其转换为连接因果关系的布尔图,即所谓的因果图
- 给图加上注解符号——说明由于语法或者环境的限制而不能联系起来的“因”和“果”
- 通过仔细跟踪图中的状态变化情况,将因果图转换为一个有限项的判定表,判定表每一列代表一条测试用例
- 将判定表中的列转换为测试用例
错误猜测
根据直觉和经验判断出错的可能类型,之后编写测试用例去暴露错误
测试策略
- 需求原型包含输入条件组合——首选因果图分析方法
- 任何情况下都要用边界值分析法——这是针对输入和输出边界的分析
- 为输入和输出确定好有效和无效等价类
- 错误猜测,增加测试场景
- 针对上述测试用例集,检查程序的逻辑结构
模块(单元)测试
模块测试——对程序中的单个子程序、过程进行测试,模块测试动机有三:
- 将注意力集中在程序的小单元上,是一种管理组合的测试元素的手段
- 减轻调试难度,错误出来即可精确定位到具体的模块
- 提供了同时测试多个模块的可能,为软件测试引入了并行工程
测试用例设计
单元测试整体是面向白盒测试的,需要两种信息:
- 需求原型
- 模块源代码
测试用例设计过程大概如下:
- 使用一种或多种白盒测试方法分析模块的逻辑结构
- 使用黑盒测试方法对照模块的规格说明以补充测试用例
增量测试
执行模块测试过程,有两点考虑:
- 如何设计一个有效的测试用例集
- 将模块组装成工作程序的方式
- 模块测试用例的编写形式
- 可能用到的测试工具类型
- 模块编码于测试顺序
- 生成测试用例的成本以及调试的成本
通过下边两个问题引入了增量和非增量测试的思考:
- 非增量测试/崩溃(big-bang)测试——是否先独立测试每个模块,再将模块组合成一个完整的程序?
- 增量测试(集成)——是否先将下一步要测试的魔铠组装到已经测试完成的模块集合中,然后进行测试?
假设上图展示的为一个程序的各个模块,我们需要测试B:
传统的非增量测试:
需要一个特殊的驱动模块(driver module)和一个或多个桩模块(stub module)来共同完成
驱动模块是为了将测试用例中的参数输入到B中,同时我们可以观察到B模块调用了E,所以需要一个额外的组件以代替E模块供B调用,这就是桩模块
用这种方法将每个模块测试完成后,组装成一个完整的程序
增量测试:
- 测试E、C、F模块
- 测试B、D模块,但是要将B和E、D和F模块组合在一起测试
- 以此类推直到测试完整个程序所有模块
通过以上的对比,可以得出增量测试和非增量测试的优劣势:
- 增量测试使工作量降低。不需要准备很多桩模块
- 增量测试可以比较早地发现模块中接口不匹配等相关的错误,增量测试其实相当于提前了集成测试
- 增量测试让调试变得容易
- 增量测试程度更彻底
- 非增量测试占用的机器时间更少
- 非增量测试在测试开始阶段,可以更多并行操作(所有的模块甚至可以同时测试)
自顶向下测试和自底向上测试
自顶向下的测试
从程序的顶部或者初始模块开始测试,关注两点:
- 如果程序中存在重要的关键模块,在设计模块测试序列的时候将这些关键的模块尽可能早地添加进去
- 设计模块序列的时候,应该将I/O模块尽可能早地添加进来
自底向上的测试
要求:要成为呼合乎条件的“下一个测试模块”,该模块所有的从属模块(为其调用)都已经事先经过了测试
缺陷:缺少早期程序框架的概念
自顶向下的测试与自底向上的测试比较
自顶向下:
优点
- 如果主要的缺陷发生在程序的顶层,将会非常有利
- 一旦引入I/O功能,提交测试用例会更容易
- 早起的程序框架可以进行演示,并可激发积极性
缺点
- 必须开发桩模块
- 桩模块要比最初表现的更加复杂
- 在引入I/O功能之前,向桩模块中引入测试用例比较困难
- 创建测试环境可能很难,甚至无法实现
- 观察测试输出很困难
- 使人误解设计和测试可以交迭进行
- 会导致特定模块测试的完成延后
自底向上:
优点
- 如果主要的缺陷发生在底层将会非常有利
- 测试环境比较容易建立
- 观察测试输出比较容易
缺点
- 必须开发驱动模块
- 直到最后一个模块添加进去,程序才形成一个整体
执行测试
-
测试用例造成模块输出的实际结果与预期结果不匹配,考虑两点:
- 模块存在错误
- 预期结果不对(即测试用例设计错误)
所以也要对测试用例进行评审和测试
-
自动化测试工具可以有效提升测试效率
-
执行测试过程中不仅要关注是否输出了正确的预期结果,还要关注输出这些结果的同时是否对其他模块产生了”副作用“,即程序是否执行了某些不该执行的操作
-
程序员不应该测试自己编写的程序
-
避免随意丢弃测试用例,测试用例需要按照某种格式严格归档,以备复用
更高级别的测试
程序无法实现最终用户要求的合理功能时,就发生了一个软件错误
从开发人员角度看,软件产品开发周期大致可以分为以上图中的几个部分:
- 将用户的要求转换为书面需求
- 评估可行性与成本、消除相抵触的要那估计需求、建立优先级和平衡关系,将用户需求转换为具体的目标
- 将上述目标转换为一个准确的产品规格说明
- 系统设计——完整的系统程序分割为单独的程序,定义接口
- 定义每个模块的功能、模块层次结构以及模块间接口,设计程序或程序集合的结构
- 设计一份准确的规格说明,定义每个模块的接口与功能
- 经过一个或多个子步骤,将模块接口规格说明转换为每个模块的源代码算法
站在其他角度审视上述过程中的规格说明:
- 需求规格说明——定义为什么要开发程序
- 目标——定义了程序要做什么,以及应该做到什么程度
- 外部规格说明定义了程序对用户的准确表现
- 与后续阶段相关的文档越来越详细的规定了程序是如何建立起来的
每个环节的验证工作,是为了减少开发过程中因为信息的沟通、理解和转换而产生的错误
软件测试是贯穿整个软件开发过程的,在整个周期中,不同阶段基于不同的目标需要使用不同的测试方法,例如:
- 模块测试——发现程序模块与其接口规格说明之间的不一致
- 功能测试——证明程序不符合其外部规格说明
- 系统测试——证明软件产品与其初始目标不一致
再次强调,软件测试是为了找问题的,而不是为了证明软件能够按照预期运转
功能测试
功能测试——发现程序与其外部规格说明之间存在不一致的过程
外部规格说明是一份从最终用户的角度对程序行为的精确描述
系统测试
系统测试用例的15个分类:
分类 | 说明 |
---|---|
能力测试 | 确保程序的目标功能实现 |
容量测试 | 发现处理大量数据时的程序异常 |
强度测试 | 发现在大规模负载、高强度不间断持续的数据处理中的异常 |
可用性测试 | 评估最终用户在使用软件并与软件交互时的可用性问题 |
安全性测试 | 视图破坏程序的安全防线 |
性能测试 | 评估程序的响应时间以及吞吐量瓶颈 |
存储测试 | 确保程序可以正确处理对存储的要求,包括系统的存储和物理存储 |
配置测试 | 检查程序是否能在推荐配置上流畅运行 |
兼容性/转换测试 | 评估新版本是否能兼容老版本 |
安装测试 | 确保能够在所有支持的平台上安装软件 |
可靠性测试 | 评估程序是否能达到规格说明中的运行时长和MTBF(平均故障间隔时间)要求 |
可恢复性测试 | 测试系统回复相关的功能是否按照设计要求实现 |
服务/可维护性测试 | 评估系统是否拥有良好的数据处理和日志机制,以备技术支持和调试之需 |
文档测试 | 校验所有用户文档是否准确 |
过程测试 | 对软件系统操作或维护所需设计的流程进行评估和确定 |
验收测试
验收测试——将程序与其最初的需求以及最终用户当前的需要进行比较的过程
安装测试
- 用户必须选择大量的选项
- 必须分配并加载文件和库
- 必须进行有效的硬件配置
- 软件可能要求网络联通,以便于其他软件链接
测试的计划与控制
一个良好的测试计划应该包括:
- 目标——定义每个测试阶段的目标
- 结束准则
- 进度
- 责任
- 测试用例库及标准
- 工具
- 计算机时间
- 硬件配置
- 集成
- 跟踪步骤
- 调试步骤
- 回归测试
测试结束准则
第一类:
模块测试结束:
测试用例来源于(1)满足多重条件覆盖准则(2)对模块接口规格说明进行边界值分析,产生的所有测试用例都是不成功的
功能测试结束:
测试用例来源于(1)因果图分析(2)边界值分析(3)错误猜测,产生的所有测试用例最终都是不成功的
第二类:
以确切数量描述结束测试的条件,这样就需要进行几个预测
- 预测程序中错误总数量
- 预测这些错误中有多大比例可能通过测试而发现
- 预测这些错误中有多少是由各个设计阶段产生的,以及在什么样的测试阶段能够发现这些问题
第三类:
测试过程中记录每单位时间内发现的错误数量,通过检查统计曲线的形状,决定继续该阶段测试或者结束当前阶段进入下一阶段的测试
最佳的测试结束准则不会是某一种,而应该是上述三类结束准则的组合
可用性(用户体验)测试
可用性测试基本要素
- 是否每一个用户交互设计都考虑到最终用户的理解力、教育背景以及环境压力?
- 程序的输出是否有意义、没有侮辱性词语、是否含糊不清?
- 用来校验的错误提示信息(error message)是否直白易懂?
- 用户界面上是否保持概念的一致、内部的连贯性、语法的一致性?是否符合约定俗称的使用习惯、语义和句法规律、格式、样式以及缩写习惯?
- 需要高精确性和准确度的软件系统是否提供了足够有效的输入验证?
- 系统是不是包含了太多选项,或者包含的一些选项不会被使用?
- 对于来自用户的输入,系统是否能够及时做出反应?
- 程序的操作是否很容易上手?
- 软件的设计是否有助于用户准确输入?
- 用户的操作可以轻松重复吗?
- 用户是否确定能够在众多的功能和菜单中来回切换而不发生意外?
- 软件的功能实现是否达到了设计规格要求?
可用性测试流程
- 选择测试用户——根据系统、软件面向的人群选择合适的测试用户
- 确定测试用户数量——需要考虑到成本、问题提出的类型繁杂不一等问题
- 测试数据采集
- 可用性调查问卷——常见问题形式:
- 是/否问题
- 真/假问题
- 某种程度的同意/反对
- 测试到什么程度可以结束?——依据北侧系统或部件的复杂度而定
调试
调试一般分为两个步骤:
- 确定程序中可疑错误的准确性质和位置
- 修改错误
暴力法调试
三种类型:
- 利用内存信息输出来调试
- 难以在内存区域与原程序中的变量之间建立对应关系
- 无论程序复杂与否,都会产生庞大数量的数据,且大多与调试无关
- 内存信息输出的是程序的静态快照,只能显示某一时刻程序的状态,发现错误往往需要研究程序动态状态
- 内存信息输出很少可以精确地在错误发生的地方产生,因此无法显示错误发生时程序的状态,可能会掩盖掉发现错误所需要的线索
- 通过分析输出的内存信息发现问题的方法不多
- 根据一般的“在程序中插入打印语句”建议来调试
- 主要是碰运气,并不是特别强调去思考程序中的问题
- 产生的所需分析的数据量庞大
- 需要修改程序,这可能会掩盖掉错误,改变关键的时序关系,也可能因此产生新的错误
- 对于大型程序,耗费成本太高
- 使用自动化的调试工具进行调试
- 同样主要是碰运气
- 产生庞大的待分析数据
暴力法调试都忽略了思考的过程,因此并不是特别建议首选这三种类型的方法进行调试
归纳法调试
-
确定相关数据——所有知道的程序执行的正确和不正确之处
-
组织数据
-
作出假设
-
证明假设
要确定这些假设可以完全解释线索的存在,如果无法解释,可能这些假设是无效或者不完整的,也可能还有更多错误存在
-
解决问题
演绎法调试
- 列出所有可能的原因或者假设
- 利用数据排除可能的原因
- 提炼剩下的假设
- 证明剩下的假设
- 修复问题
回溯法调试
这种方法可以在小型程序中定位错误时使用
沿着程序的逻辑结构回溯不正确的结果,直到找出程序逻辑错误的位置
测试法调试
这里要区分两种类型的测试用例:
- 测试用的测试用例——暴露出以前没发现的错误
- 调试用的测试用例——提供有用的信息,以供定位某个被怀疑的错误之用
调试的原则
定位错误的原则
- 动脑筋
- 遇到僵局,稍后解决
- 遇到困境,描述给他人
- 只将调试工具作为第二种手段
- 避免使用试验法——最后的手段!
修改错误的技术
- 存在一个缺陷的地方,很可能存在其他缺陷
- 应该纠正错误本身,而不是只修改了错误症状
- 正确纠正错误的可能性并非100%
- 随着程序规模的增加,正确修改错误的可能性反而降低
- 意识到改正错误会引入新错误的可能性
- 修改错误的过程也是临时回到设计阶段过程
- 应该修改源代码,而不是目标代码
- 这通常是“通过试验”进行调试的信号
- 目标代码与源程序不同步,即程序重新编译或者汇编后,同样的错误很容易又浮现出来,这种方法过于草率
错误分析
- 错误出现在什么地方?
- 谁制造了这个错误?
- 哪些做的不正确?
- 如何避免该错误的出现?
- 该如何更早地发现错误?
敏捷开发模式下的测试
敏捷软件开发宣言:
敏捷开发的特征
提倡迭代式和增量式的开发模式;强调测试在其中的重要作用;围绕以用户为中心、以客户需求为导向的开发过程,客户是敏捷的关键环节,没有客户的参与,敏捷等同于失败;开发过程随时做好“迎接变化”的准备
任何可以被视为敏捷开发的过程,一般都有以下三个共同点:
- 依赖客户的参与
- 测试驱动
- 紧凑的迭代开发周期
常见的敏捷开发方法有以下几种:
敏捷测试的特征
- 要求每一个人都参与到测试计划的设计、实现以及执行中去
- 敏捷测试需要客户尽早参与到开发周期中来,并一致持续到结束
- 开发者通常需要从创建单元测试开始,实现软件单元代码
- 快速软件开发需要更加及时的反馈,敏捷测试依赖于自动化测试
极限编程与测试
极限编程(eXtreme Programming,XP)是当前最流行的敏捷软件开发过程,XP模型高度依赖模块的单元和验收测试,所以需要首先创建单元(模块)测试和验收测试,然后再创建代码库,这种形式的测试成为极限测试(eXtreme Testing,XT)
极限编程基础
XP是一种可以使开发人员快速生产高质量代码的软件开发过程,这种情况下,“质量”可以被定义为代码库对其设计的规格以及客户的满意程度
XP关注以下几点:
- 实现简单的设计
- 开发人员与客户的沟通协作
- 不断测试代码库
- 重构以适应规格说明的变更
- 寻求用户的反馈
XP开发模型有12个核心实践来驱动:
以上核心实践可以归纳为4个概念:
- 聆听客户和其他程序员的谈话
- 与客户合作,开发应用程序的规格说明和测试用例
- 结对编程
- 反复测试代码库
我们对极限编程实践的计划和测试可以进一步讨论:
- XP计划:XP计划重点确定客户应用需求,之后设计使用场景(User Story)满足客户应用需求,这些场景在最后的验收测试阶段也可以为用户的操作提供参考,同样,这种让用户深入参与的模式可以增强用户对于程序的拥有感和信息
- XP测试:基于XP方法取得成功的关键是进行连续的测试,这种测试虽然包含验收测试,但是最主要的部分还是在于单元测试。所以确保编写的单元测试可以成功、准确地捕获错误是重中之重,在单元测试可靠时,开发人员年编写模块代码也更加有底气
极限测试:概念
为了满足XP的流程和思想,开发人员会使用极限测试方法,一种强调连续测试的方法
前边已经提到过,这种方法包含两种类型的测试:单元测试和验收测试
-
极限单元测试:
极限单元测试有两条非常简单的规则:
- 所有代码模块在编码之前就必须设计好测试用例
- 在产品发布之前必须通过单元测试
这样做的好处有:
- 获得了代码将满足其规格说明的信心
- 在开始编码之前,就展示了代码的最终结果
- 更好地理解了应用程序的规格说明和需求
- 可以先实现一些简单的设计,稍后放心重构代码以改善程序性能,无序担心破坏应用程序的规格说明
-
验收测试
目的在于判断应用程序是否满足诸如功能性和易用性等其他需求,在设计/计划阶段,由开发和客户设计验收测试
即使程序通过了所有的单元测试,也不一定百分百通过验收测试,因为通过单元测试只是保证了程序满足了规格说明中的内容,但是具体的产品外观、使用感觉是验收测试中用户的实际反馈
互联网应用测试
电子商务的基本结构
典型的电子商务网站结构
第一层运行Web网站,web服务层为三层结构的第一层,也称为“表示层”,该层将可视化了的内容提供给最终用户,web服务器可以使用静态超文本标志语言(如HTML)或者通用网关接口(CGI)脚本生成动态HTML,大多数情况下可能组合使用静态和动态页面
第二层又称“业务层”,运行应用服务器,与业务层相关的功能常见如下:
- 事务处理
- 用户身份鉴定
- 数据确认
- 程序日志
第三层核心是从数据源(通常为一个关系数据库管理系统RDBMS)中存储和获取数据,又称为数据层
测试的挑战
设计和测试基于互联网的应用系统会有很多不可控因素,给测试带来很多挑战,下面列举一些可能会遇到的挑战:
-
用户群庞大
用户使用网络应用的能力参差不齐,使用的浏览器、操作系统、设备不尽相同。且用户访问网站的信道速率差别也很大
-
业务环境
如果运行的是电子商务网站,还需要考虑计算税费、运输成本等问题
-
地点
用户可能处于不同国家,涉及网络、语言、时差、货币兑换等国际化问题
-
安全性
由于网站通常是公开的,所以必须避免黑客攻击
-
测试环境
为了严格测试应用系统,必须复制软件运行的环境,包括网络环境,如路由器、交换机、防火墙
针对上一环节中电商网站的三层结构,我们应该将测试集中在以下领域:
表示层
- 确保不同字体在不同浏览器中都相同
- 检查以确保每一个链接都指向正确的文件或站点
- 对每一页进行拼写检查
- 让文字编辑检查语法和风格
- 在页面载入时检查光标位置,以确保其在正确的文本框中
- 检查以确保在页面载入时选中了默认的按钮
- 检查交互性操作的用户友好度反馈以及体验一致性
- 检查商业或行业的特定术语与风格的使用
业务层
- 检查关于费用的计算是否正确
- 确保提出的响应时间、吞吐率等性能指标得到了满足
- 确保失败的事务回滚正确
- 确保正确采集数据
数据层
- 确保数据库操作满足性能要求
- 验证数据存储适当且正确
- 验证可使用当前备份来恢复
- 测试故障处理和冗余功能
- 测试数据加密和安全性(设计财务账户、私人信息等隐私)
- 测试后端数据输入与管理功能的可用性以及准确性
对于互联网开发者,参考一下“四不要”原则:
- 不要妄自揣测自己编写的应用的使用群体,因为用户可能是任何一个人
- 不要以为每个访问者都跟自己一样精通计算机知识
- 不要乐观以为用户因为网站导航体验不舒服还可以保持浏览的兴趣,一定要重视易用性和人机交互界面
- 不要天真认为自己了解了用户对于性能和信息的所有终极需求
此外我们还应该注意以下常见的影响互联网用户使用体验的关键事项:
- 一定要注重用户使用系统的第一印象, 包括易用性、人机交互界面、功能操作逻辑、性能,对于互联网用户来说,及时得到满足是非常重要的,因此几秒的延迟都可能严重影响用户体验,所以必须要确定好性能指标,并针对性地设计测试暴露导致网站无法满足指标的原因
- 用户在网站上进行购买交易等服务时,要求交易进行得迅速、准确、正确
- 软件要确保采集到的数据(如电话号码、金额、邮件等)是有效的,这就需要保证获取到的数据属性(如字符串长度、格式、完整性等)是可用的
- 互联网环境的应用要保持网站对客户的可用性
- 网络连通性也是重要测试对象
测试的策略
基于互联网的应用系统最好采用“分而治之”的方法测试,其结构也允许我们选出某个单独区域进行测试,下边给出互联网应用基本三层结构的详细视图:
- 表示层:互联网应用系统的这一层提供了GUI(图形用户接口)
- 业务逻辑层:模拟业务流程,如用户身份验证、事务处理等
- 数据访问层:存储了共供应用系统使用的或从最终用户收集来的数据
针对这些结构中的具体区域,我们可以对每一层进行如下测试:
- 可用性/人机界面
- 检查整体的外观和感觉
- 字体、图形和色彩对于应用程序整体美感起着重要作用
- 确保所有的用户输入要予以确认,由此可以明明白白地让用户知道输入的数据有没有被接收
- 性能
- 检查快速载入页面
- 检查快捷的事务
- 较差的性能往往造成不好的印象
- 业务规则
- 检查对业务流程的描述是否准确
- 考虑目标用户群的业务环境
- 确保术语和风格遵循了行业规范
- 事务准确性
- 确保事务正确完成
- 确保被取消的事务回滚正确
- 对用户输入进行验证是否能满足安全性和准确性需求
- 数据的有效性和完整性
- 检查电话号码、电子邮件、金额等格式是否正确
- 确保字符集适当
- 系统可靠性
- 检查程序、网站和Web服务器的故障处理能力
- 最大化MTBF,最小化MTTR
- 网络结构
- 测试连通冗余
- 测试网络中断时程序的表现
表示层的测试
本层的测试主要目的是发现应用程序的GUI或前端中的错误,具体可以氛围一下三个内容:
- 内容测试——审美、字体、色彩、拼写、内容准确性、默认值等
- Web站点结构——包括无效的链接和图形
- 用户环境——包括Web浏览器版本和操作系统配置,也称为浏览器兼容性测试
业务层的测试
重点发现系统业务逻辑中的错误
-
性能测试——测试系统的鲁棒性和可测量性
常用强度测试方法来测试系统性能。通过大量登录操作“轰炸”系统或模拟大量事务逼近系统的邻近失效点,以此结果判断系统是否满足性能能指标
-
数据验证
-
事务测试——以一个电商系统为例,一个用户的购买活动通常包括:
- 搜索商品分类
- 整理购物车
- 向用户推荐感兴趣上去品
- 向用户推荐其他用户浏览过的商品或店铺
- 抓取当前用户浏览过的商品信息
- 创建/登录账号
- 购买商品
- 通知用户交易完成(短信、邮件等形式)
数据层的测试
- 响应时间测试——量化结构化查询语言SQL语句的消耗时间
- 数据完整性——确保数据存储时适当且正确的
- 容错性和可恢复性
移动应用测试
移动应用测试需要考虑以下因素:
- 连接
- 设备硬件配置
- 网络速度
- 网络延时
- 偏远地区网络可用性
- 服务可靠性
- 设备多样性
- 需要测试众多浏览器
- 针对不同语言(Java、C等)的运行库版本
- 设备限制
- 有限的处理器和内存资源
- 屏幕尺寸
- 多操作系统
- 对多任务应用的支持能力
- 数据缓存大小
- 输入设备
- 触摸屏
- 触摸笔
- 鼠标
- 按键
- 滚轮
- 安装和维护
- 安装&卸载
- 打补丁包
- 升级应用