东南大学《软件测试》课程复习笔记
2020-05-27
惊闻这两天就要安排线上考试,抓紧把之前剩了个尾巴的复习笔记完成了,希望可以取得还OK的成绩。
整理了一下2019-2020学年计算机学院大三下《软件测试》课程的内容,仅供参考。
by z0gSh1u
导论 / Chapter 1
大型软件开发中经常遇到巴别塔和焦油坑(极度混乱、复杂的情况)
什么是错误(error):1+2=5;死机、崩溃…
为什么有错误:Everything & Everyone。软件测试就是为了发现错误以便解决错误。
软件测试过程图
正确的测试策略:尽可能经常测试,并且尽早测试。缺陷发现的越晚,消除的代价就越大。
经典测试理论 / Chapter 2
测试的V模型
提出的四大测试过程:单元测试、集成测试、系统测试、验收测试
问题:错误引入的越早,发现的却越晚。
单元测试
对最小可测试元素(函数、类)进行测试。
- 模块接口测试:检查进出模块的数据是否正确
- 模块局部数据结构测试:未使用的变量、数组越界…
- 模块边界条件测试:合法数据、非法数据…
- 模块独立执行通路测试:计算错误、判定错误、控制流错误
- 模块内部错误处理测试:错误处理设施是否有效
集成测试
对单元进行集成后测试。测试对象可以是一个包或一组包。
进行集成的方法
-
增式
A → AB → ABC
特点:工作量小;发现错误早;错误定位容易;测试彻底;需要机器多;并行性差
-
自顶向下
高级模块作为驱动,每次替换掉一个低级的stub(桩,临时占位的模块),逐步求精。
缺点:需要提供stub;stub模块可能难以模拟数据
-
自底向上
要编制驱动程序,协调测试用例输入输出。生成测试数据没有困难,适合关键模块在底部的情况。
缺点:不能尽快看到整个程序的框架;时序和资源竞争问题只能到后期发现
-
-
非增式
特点:工作量大;发现错误晚;错误定位难;测试不彻底;需要机器少;并行性好
A → B → C → ABC
系统测试
对系统“整体”进行测试,模拟真实运行环境。
验收测试
最后一道工序,用户在场测试,非开发人员和测试人员完成。
-
α测试(内测)
开发即将完成时进行,可以对程序作小的变动。测试环境受开发方控制,用户的数量少,时间集中。
-
β测试(公测)
开发基本完成时进行,正式发布前寻找错误。测试环境不受开发方控制,用户的数量多,时间不集中。
测试任务的过程
测试计划
-
测试目标
单元测试的目标是检查最小单元有无错误;集成测试的目标是检查模块有无错误…
-
测试范围(回答“哪些“的问题)
哪些接口需要测试?哪些要做性能测试?
-
测试项目
功能测试;数据库集成测试…
-
测试策略
基于代码覆盖;基于规格说明…
-
测试工具
-
测试资源
-
产出物件
测试设计
- 设计测试用例
- 设计测试脚本
- 评估测试覆盖
开发
- 搭建测试环境
- 编写测试脚本
执行
- 执行测试脚本
- 分析执行情况
- Bug报告 / Bug跟踪
评估
- 分析覆盖率(测试用例覆盖、代码覆盖)
- 分析缺陷
- 是否达到退出标准
- 撰写报告
测试方法
-
静态方法和动态方法
- 静态分析:检查和阅读
- 动态方法:运行测试用例
-
黑盒测试与白盒测试
- 黑盒测试:不考虑程序内部
- 白盒测试:分析程序的内部结构
-
回归测试
检查修改或增加的部分是正确的,并且没有造成其他错误
-
模拟用户操作
测试类型
- 可靠性测试
- 功能测试
- 性能测试
- ……
测试工具
- 测试管理工具:Quality Center
- 性能测试工具:Load Runner
- 单元测试工具:JUnit
黑盒测试 / Chapter 3
将程序当作黑盒,进行总体功能验证。
特点:基于规格说明(需求),与代码实现无关,以用户视角进行,适用于测试的各个阶段。
实施工具:需求规格说明;需求跟踪矩阵RTM;测试执行数据
实施策略:正面(预期输入输出)和负面(非预期输入)测试用例
等价类划分
将程序的输入域划分为有效等价类(有效输入数据)和无效等价类(无效输入数据),来导出测试用例。
划分准则
下表描述了一些输入数据类型的等价类划分思想:
例子:
合法的用户名要求:
(1)由字母开头;(2)后跟字母或数字的任意组合构成;(3)有效字符数不超过6个2个有效等价类:
(1){0<全字母<=6}:John, Kenedy
(2){0<字母开头+数字<=6}:u001, user014个无效等价类:
(1)字母串,且长度超过6:userabcd
(2)字母开头的字符、数字组合串,且长度超过6:user01587
(3)长度不超过6的数字开头串:010ah
(4)其他:数字开头的字符串集合、空字符串、非法字符串
因果图(判定表)
多个输入条件互相关联、存在逻辑关系时,组合测试可能生成大量无效的测试用例。
因果的四种关系
四种输入约束,一种输出约束
输入:
- 互斥:最多只有一个成立
- 包含:至少一个成立
- 唯一:有且只有一个成立
- 要求:有向,C1成立则C2成立
输出:
- 屏蔽:有向,E1成立则E2不成立
根据因果图,可以快速排除一些不可能的输入,和一些不可能的输入输出组合。
边界值分析
利用并扩展了缺陷更容易出现在边界处的概念。
等价类划分时,往往先要确定边界值。边界值分析是等价类划分方法的补充,测试中需要将两者结合起来使用。
Paul Jorgensen公式
n为存在边界值的参数个数。
-
4n+1:基本边界测试
每个参数取min、min+1、max、max-1各一次,其他参数取典型值;最后全部参数取典型值
-
6n+1:健壮性边界测试
每个参数取min、min±1、max、max±1各一次,其他参数取典型值;最后全部参数取典型值
-
3m:条件边界测试
每个条件取自身、±1各一次
白盒测试 / Chapter 4
白盒测试就是结构化测试。两个特点:
- 基于代码
- 尽可能覆盖实现的行为
概论
静态白盒测试
不执行软件,审查软件设计、体系结构和代码。
特点:
- 可发现某些机器发现不了的错误
- 利用不同人对代码的不同观点
- 节约计算机资源,但以增加人工成本为代价
动态白盒测试
提供源代码和可执行程序,测试过程需要在计算机上执行程序。
优点:
- 能检测代码中的判断和路径
- 对代码的测试比较彻底
缺点:
- 无法检测不可达路径
- 不能验证需求规格
覆盖准则
我们不可能进行穷举测试,覆盖准则回答了“测试执行到何时才是足够”的问题。
基于控制流的动态白盒测试方法
语句覆盖
测试用例能使得每个可执行语句都至少执行一次。
例子:
问题:
- 100%的语句覆盖很困难,可能存在不可达代码
- 可能无法发现一些严重问题
判定覆盖
测试用例使得被测程序的每个判定分支至少经过一次(即真假至少取一次)。
特点:
- 判定覆盖包含了语句覆盖,并避免了遗漏边的问题
- 不能发现条件表达式中的错误
条件覆盖
保证每个判断中的每个原子条件的可能取值(不是每种组合)至少满足一次。
例:if A and B then Action1
则(1)A=true,B=true(2)A=false,B=false 两个用例即满足要求
条件覆盖不能保证程序所有分支都被执行。
条件组合覆盖
保证每个条件的取值组合至少出现一次。问题是代价昂贵(2^n)。
某些条件组合是不可能的(如F OR F = F)。
判定条件覆盖
每个条件的所有可能取值至少一次(条件覆盖),每个判断本身所有可能结果也至少一次(判定覆盖)。
路径覆盖
保证每条可能执行到的路径都至少经过一次。
优点:相对彻底;缺点:路径数可能指数级增加(2^n,n是分支次数);可能存在不可达路径
基本路径测试
寻找基本路径,保证每条路径至少执行一次。
-
从流程图到流图
流图:描述程序中的逻辑控制流(顺序结点可以合并)
-
寻找基本路径
基本路径:贯穿程序的、至少引入一组新的处理语句或一个新判断的程序通道
例:
圈复杂度:度量基本路径数,也是所有语句被执行一次所需测试用例数的下限。高圈复杂度的模块蕴含错误的可能性最大,是测试中关注的焦点。
基本路径集合可能有多种,但基本路径数是确定的,等于圈复杂度。
寻找基本路径:
- 找到从入口到出口的最短路径
- 递归地改变该最短路径上所有判定结点的真假情况,生成新的路径
- 直到找到所有基本路径
-
产生测试用例
根据基本路径设计测试用例进行测试。
基本路径的本质
每一条路径可以表示成边的向量,Px=(e1, ..., en)(n为边数,元素为该边在路径中的出现次数)。其他任何路径对应的向量,都可以用这些基本路径的向量线性组合得到。这说明只要基本路径测试正确,则其他逻辑也测试正确。
循环的处理
-
简单循环
跳过;执行一次;执行两次;执行m次;执行n-1、n、n+1次(n为循环次数,m<n)
-
嵌套循环
- 先测试最内层循环,此时外层的循环变量取最小值,内层按简单循环测试
- 从内往外测试上层循环,更外层的取最小值,更内层的取典型值,该层按简单循环测试
- 最后全体按同时取最小或最大循环次数测试
-
串接循环
- 串接互相独立:分别用简单循环方法测试
- 串接不独立(第一个循环变量与第二个循环控制相关):把第一个看作外循环,第二个看作内循环,按嵌套循环测试
-
非结构循环
需要先进行结构化。可以使用重复编码法、状态变量法、判定转移法。
基于数据流的动态白盒测试方法
根据程序中变量定义和其后变量使用的位置来选择程序的测试路径。
一些定义:
-
P:程序
-
G(P):程序流图
-
V:变量集合
-
PATH(P):P的所有路径集合
-
DEF(v, n):变量v的定义节点是n
-
USE(v, n):变量v的使用节点是n
- P-USE:谓词使用,即在条件判断语句中使用
- C-USE:运算使用,即在计算表达式中使用
- O-USE:输出使用
- L-USE:数组定位使用
- I-USE:循环迭代次数控制使用
-
DU-PATH:定义-使用路径,是PATH中的某条路径,以DEF起始,中间可有其他DEF,到USE终止
-
DC-PATH:定义-清除路径,DEF起始,中间没有其他DEF
测试过程
- 分析出DEF和USE节点,记DEF节点数为NDEF,USE节点数为NUSE
- 列出可能的DU-PATH(NDEF*NUSE条)
- 约简DU-PATH,对于相同前缀的,只保留最长的
- 针对每个剩下的DU-PATH设计测试用例
Q问题
面向路径的测试数据生成问题——是否存在一组输入,使得P中任意一条语句可被执行,在理论上是不可判定的。
解决方法:
- 随机法
- 静态法
- 符号执行:用符号值表示程序变量,然后模拟程序执行
- 区间算术:数值和变量都用区间表示,然后进行区间消减
- 迭代松弛法
- 遗传算法
- 模拟退火
面向对象软件测试 / Chapter 5
面向对象核心概念
-
对象(实例):可操作性的实体
-
消息:对象之间通过消息传递来协作
-
接口:对象行为声明的集合
-
类:具有共性的对象的集合
-
封装
-
继承:类的依赖关系
-
多态:重载,动态绑定
OO软件测试概论
与传统测试的异同
- 单元测试时基于两个基本元素:METHOD和CLASS
- 系统测试与传统测试类似,仍然基于需求规约
- 集成测试是OO测试最复杂的部分
面向对象技术对软件测试的影响
-
类的使用
使得基本可测单元是类或对象,而不是子程序。在对每个类进行单元测试后还要对类簇进行测试。
-
封装的使用
信息隐藏使得对象的一部分不可访问,减轻了波动影响。但测试时经常需要访问对象的内部状态,信息隐藏给这带来了难度。
修改时需要大量的回归测试。
-
继承的使用
要确定衍类中从基类继承的已测试的功能是否需要再测试。
-
多态和动态绑定的使用
引入了不可判定问题:难以确定该用例使得哪个多态方法被激活;多态组件的每个可能的绑定都需要独立测试,而实际上又难以找到所有的绑定。
状态的控制分布在整个系统中,使得难以对每个状态进行单一的测试。
-
抽象的使用
要执行内部的结构测试时,降低了软件的可测试性。
OO软件测试
OO软件的开发过程
采用迭代式增量开发过程模型,每次迭代包含OOA(分析)、OOD(设计)、OOP(编程)三个阶段。
面向对象测试模型(OOTM)
在整个软件开发生命周期全过程中不断测试。
-
OOA Test、OOD Test
测试分析和设计的结果,由建模专家、领域专家、软件专家参与
-
OOP Test
针对编程风格和程序代码进行测试,采取单元测试、集成测试等手段
-
OO Unit Test
对具体单一的功能模块的测试(类)
-
OO Integrate Test
对系统内部的相互服务进行测试(函数间、类间合作)
-
OO System Test
最后阶段的测试,主要以用户需求为测试标准
面向对象单元测试(OO Unit Test)
面向对象的单元测试,实际就是对类的测试。
-
基于服务的类测试策略
考察类中的方法对数据的操作。
为了避免软件测试的盲目性,Kung提出了块分支图法(BBD)
一个方法f的BBD是一个五元组
(修改前的类Du, 修改后的类Dd, 参数表P, 调用的服务Fe, 控制结构图G)
:构造BBD图后可对程序的流程进行抽象进行测试:
- 绘制服务的控制流图
- 确定基本路径集(每个可执行语句至少一次)
- 生成测试用例
-
基于状态的类测试策略
从外界向对象发送特定消息序列,来测试对象的响应状态。利用对象状态图(OSD)模型来测试:
OSD描述了对象在其生命周期中的所有状态及其状态之间的相互转移。构造过程如下:
- 扫描源程序,得出执行分析表
- 确定对象状态
- 构造状态转移
- 构造测试消息序列
- 生成测试用例(根据每条测试消息序列,选择相应的数据进行测试)
面向对象的集成测试(OO Integrate Test)
由于程序的控制流无法确定,只能对编译完成的程序做基于黑盒的集成测试。要参考OOD的结果。
-
静态测试
得出源程序的类系统图和函数功能调用关系图,检测程序结构是否符合设计要求(OOP是否符合OOD要求)
-
动态测试
根据静态测试得出的图表,设计测试用例,达到一定的覆盖标准
设计测试用例的方法:
- 确定类的状态和行为
- 确定待测类的所有关联
- 根据类的对象构造测试用例,使用一定的输入激发某种状态,也要设计类禁止的负例
面向对象的系统测试(OO System Test)
测试软件与系统其他部分配套运行的表现。要参考的OOA的结果。
主要测试内容:
功能测试;强度测试;性能测试;安全测试;恢复测试;可用性测试;安装/卸载测试
性能测试 / Chapter 6
引言
性能需求是一种非功能性需求。
性能测试是模拟正常、峰值、异常负载条件下,测量系统的性能指标能否达到需求,以找出瓶颈,优化系统。
性能测试的步骤
- 熟悉应用:以知道需要模拟什么
- 测试需求:可能需要进行指标的转换(UV如何转换成吞吐量、响应时间指标)
- 测试准备
- 机器数量
- 网络环境:带宽应高于服务器吞吐量,避免出现瓶颈
- 测试数据准备:用户注册、数据量
- 测试用例设计
- 测试脚本准备
- 测试执行:监控客户端和服务器性能,后续归档处理
- 测试结果分析:内存问题、共享资源竞争问题
性能测试的指标
-
响应时间
网络传输时间+应用服务器处理时间+数据库处理时间
-
并发用户数
-
吞吐量
- 业务视角:请求数/秒、页面数/秒…
- 网络视角:字节/秒
-
资源利用率(内存、磁盘、处理器、网络)
实际使用/总量
性能测试的方法
-
基准测试
制定一个性能基准测试标准。系统变化之后,进行一次相同标准的测试,即可看出变化对性能的影响
-
负载测试
找出负载逐渐增加时系统性能指标的变化情况,找出系统的负载极限
-
压力测试
找出高负载系统下的问题,如资源竞争、同步问题、内存泄漏
-
容量测试
找出能正常运行的最大负载或工作量
-
失效恢复测试
局部发生故障,能多大程度上继续使用系统
-
并发测试
-
……
例子 - 压力测试
相关工具:LoadRunner、QALoad…
安全测试 / Chapter 7
引论
基本概念
- safety安全可靠:软件在运行过程中不到达某个不安全的状态
- security安全保密:软件不会被黑客攻击
- vulnerability漏洞:系统设计、实现或操作和管理中存在的缺陷或弱点
为什么要关注软件安全
- 黑客攻击机会越来越多,越来越容易
- 漏洞利用速度越来越快
- 病毒的种类和感染的机会越来越多
软件安全问题
安全问题 | 例子 |
---|---|
内存安全 | 缓冲区溢出问题可能导致攻击者控制内存空间,执行恶意代码 |
进程/线程安全 | 线程同步问题、线程协作问题、线程死锁问题、线程控制问题… |
异常/错误处理 | 异常捕获安全、异常处理安全… |
输入安全 | 不要信任用户的任何输入… |
国际化安全 | 字符集转换安全 |
面向对象安全 | 对象的内存分配与释放、序列化安全… |
Web安全 | XSS攻击、SQL注入… |
访问控制不当 | 特权提升问题… |
远程调用安全 | 恶意调用RPC服务器中的过程… |
拒绝服务攻击 | DoS、DDoS… |
数据加密保护 | 加密算法不良、密码保存不安全… |
代码注入 | DLL注入… |
产生的原因:观念不足;软件设计不好;编码错误;测试不到位…
解决之道
先验方法
-
让每个人都参与进来
软件安全观念教育;同步行业安全问题
-
主动的安全开发过程
- 攻击面最小化:尽量减少暴露给用户的可受攻击的地方
- 威胁建模:对最可能影响系统的威胁进行系统的识别和评估
后验方法
-
软件安全测试
一般测试以发现BUG为目标,安全测试以发现安全隐患为目标。
-
渗透测试
通过模拟恶意黑客的攻击方法,来评估计算机网络系统安全。
回归测试 / Chapter 8
在软件日常维护、迭代更新中,需要对变化的部分加以测试。回归测试就是仅对修改的部分进行测试,并且保持原有测试覆盖的方法。
引言
过程步骤
- 提出修改需求
- 修改软件制品(artifact)
- 选择测试用例,执行测试
- 识别失败结果
- 确认错误,排除错误
策略
-
全部重测
优点:不用花精力选择测试用例;缺点:耗时较长,可能产生浪费
-
有选择的重测
优点:适合小部分的修改;缺点:需要选择测试用例
开销
- 两种测试用例
- T1:为了测试改变的代码的新测试用例
- T2:原有测试用例要进行有效性重确认
- 提高效率的实用性建议
- 使用工具
- 考虑依赖性(模块依赖、修改依赖)
波及效应分析(REA)
因为软件工件间的依赖性,所以需要REA来发现被影响到的部分。
波及效应分析的两种方法
- 字符串匹配或交叉应用
- 程序切片
波及效应的分类
- 直接波及
- 诱发波及
如果直接波及后不需要进一步的更改,那么诱发波及就不需要分析。