软件构造——测试
软件构造——测试
软件测试
- 软件测试的含义
- 测试的意义:通过发现bugs, 确认是否达到可用级别(用户需求) 来提高软件质量
- 残留缺陷率:任何测试都无法达到0错误,因此我们将每1000行被测试用例覆盖的代码中剩余的bugs称为残留缺陷率,当程序规模变得很大时即便有很小的残留缺陷率依然可能意味着有巨量的bug。以下是几个典型的残留缺陷率数据
- 1-10:典型工业软件
- 0.1-1:Java库
- 0.01-0.1:NASA、Praxis这样(要求极高,出错代价极大的程序)
- 测试的特征
- 测试的目标:测试与我们传统编写程序目标相反,更希望程序出错
- 测试的局限性:没有测试可以证明系统0错误
- 测试良好的特征:
- 能尽可能多的找出程序中存在的错误
- 可以找出错误的同时测试代码设计不冗余
- 在所有相似的测试中是最有效的
- 不会过于简单也不会过于复杂
- 测试的种类
- 单元测试:针对程序中的代码段进行测试,也可以说是函数层次
- 集成测试:针对多个开发者分别开发的子系统结合起来进行测试
- 系统测试:针对整个系统的测试
- 测试的区分——静态测试与动态测试
- 静态测试:用眼睛看,不实际执行
- reviews, walkthroughs, inspections
- 动态测试:使用给定的测试用例集对代码进行实际测试
- stubs, drivers, execution from a debugger environment
- 测试的区分——测试与调试
- 测试:确定程序是否存在错误
- 调试:在已知程序存在错误的情况下,通过调试进一步确定错误根源
- 测试的区分——黑盒测试与白盒测试
-
黑盒测试:
- 不知道程序源代码,只知道程序总体功能,对程序输入和输出进行测试
- 更类似于程序上线后用户不清楚程序内部代码但是可以通过判断程序输出是否符合自己预期来测试程序。
-
白盒测试:
- 知道程序源代码,对程序内部函数等子模块进行测试
- 更类似于开发者们在开发阶段知道程序源码进行的单元测试、集成测试等
- 测试的难点
- 测试程序复杂度限制:穷举程序中所有可能出现的情况是不可能的
- bug之间的差异性
- 由于有些bug就是要比其它bug更加隐蔽所以无法通过统计评估残留缺陷率,所以基于样本的统计数据对软件测试意义很小
- 同样的原因,偶然测试是没有意义的(除非程序的低级错误太多)
- 测试的要求
-
测试程序可以让更多的程序出错
-
测试程序在让程序出错的基础上可以达到快速让其出错
测试用例
-
测试用例的概念:测试用例 = 测试输入 + 执行条件 + 预期输出
-
测试用例的评价指标
- 完备性:最可能发现错误
- 最小化:不冗余
- 局部最优:在所有相似的测试用例中是最有效的
- 复杂度适中:不会过于简单也不会过于复杂
- 测试用例领域的研究前沿:
- 根据规约和代码自动生成测试用例
- 最小化测试用例
测试优先编程
- 测试优先编程的概念:先写测试用例再写代码
- 测试优先编程的原因
- 更深入了解需求:更早发现规约中不完备的地方,避免浪费时间
- 减少调试时间:让代码更快出错,先编程再测试当出错时修改成本非常高
- 测试优先编程的流程
- 规约: 写规约spec
- 测试用例:写符合规约spec的测试用例
- 程序代码:写代码、不断修改直至通过测试用例
-
规约的概念:对函数输入和输出行为的描述
-
规约的组成:
- 对函数输入参数的限制描述
- 对函数功能的描述(输入与输出之间的映射关系)
- 对函数返回值类型和限制的描述
单元测试
-
单元测试的概念:分隔程序中各个子模块,对程序中的最小单元模型进行测试
-
单元测试的优势:更容易定位bug位置,方便下一步的调试
-
单元测试的要点:
- 测试模块接口:确保模块的输入输出数据类型符合规约
- 测试本地数据结构:确保模块中的不变量在算法执行后保持其一致性
- 测试所有语句:确保所有语句都被测试到
- 测试边界条件:确保程序对边界情况处理正确
使用JUnit进行自动化单元测试
- JUnit的介绍:
- JUnit在测试驱动开发中非常重要,它是知名测试框架家族xUnit中的一个
- 在编译时JUnit作为外部jar包被链接到源程序中
- JUnit3.8及以前,该框架在junit.framework包中;JUnit4在org.junit包中;JUnit5在org.junit.jupiter.api包中
- JUnit测试用例的规范
- 在每个测试用例方法前都需要用@Test进行标注
- 使用断言assert类方法进行测试
- JUnit中assert方法介绍:
- assertArrayEquals("failure - byte arrays not same", expected, actual);
- assertEquals("failure - strings are not equal", "text", "text");
- assertFalse("failure - should be false", false);
- assertNotNull("should not be null", new Object());
- assertNotSame("should not be same Object", new Object(), new Object());
- assertNull("should be null", null);
- assertSame("should be same", aNumber, aNumber);
- assertTrue("failure - should be true", true);
- assertThat("albumen", both(containsString("a")).and(containsString("b")));
- assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three"));
- assertThat("good", allOf(equalTo("good"), startsWith("good")));
- JUnit进行测试的方法:
- 待测试的方法存储在./src目录下
- 对应测试用例存储在./test目录下(两者要在同一个源文件夹中)
- 测试用例编写按照上述规范即可
黑盒测试
- 黑盒测试的概念:不关心程序内部实现只检查在指定输入下程序输出是否符合预期
- 黑盒测试的错误类型:
- 错误或丢失的函数功能
- 函数接口错误
- 数据一致性或外部数据库连接错误
- 函数行为错误
- 初始化或终止行为错误
- 黑盒测试用例的编写方法:
-
等价类划分:
- 方法描述:针对每个输入数据需要满足的约束条件,划分等价类 (如果一组对象间存在对称、传递和自反的关系,则认为是等价类),从等价类中导出测试用例。
- 方法原理:相似的输入会展示相似的行为,故可从每个等价类中选一个代表作为测试用例即可
- 方法目的:最小化测试用例数量
- 方法分类:
- 输入数据限定了数值范围:划分范围内为一个有效等价类,范围外小于和大于共两个无效等价类
- 输入数据指明了特定的值:划分指定值为一个有效等价类,不为此值的为一个无效等价类
- 输入数据确定了一组数值:划分指定值集合为一个有效等价类,不在此集合中的为一个无效等价类
- 输入数据是True/False:划分一个有效等价类和一个无效等价类
-
边界值分析:
- 方法原理:大量错误发生在输入域的边界而非中央
- 方法目的:更快找出错误
- 方法分类:
- 正数与负数的边界:0
- int,double类型的边界:max,min
- 抽象数据类型的边界:空与非空
- 抽象数据类型的边界:第一个元素和最后一个元素
-
覆盖划分的方法:
- 全覆盖
- 将所有情况使用笛卡尔积全覆盖
- 测试用力多,测试覆盖度高
- 覆盖一次
- 每个维度的每个取值至少被一个测试用例覆盖一次
- 测试用例少,测试覆盖度可能低
- 实际中通常结合以上两种方法,基于人的判断做出选择,也会被白盒测试和代码覆盖工具影响
- 全覆盖
白盒测试
- 白盒测试的概念:知道程序源代码,对程序内部函数等子模块进行测试
- 白盒测试的特点:
- 根据程序执行路径设计测试用例
- 白盒测试可以在单元、集成、系统水平进行测试
- 白盒测试一般较早执行
- 一个典型的白盒测试方法(独立/基本路径测试)对程序所有执行路径进行等价类划分,找出有代表性的最简单的路径(例如循环只需执行一次),设计测试用例使每一条基本路径被至少覆盖一次
测试覆盖度
- 代码覆盖度的概念:已有的测试用例多大程度覆盖了被测程序
- 代码覆盖度与测试的关系:代码覆盖度越低,说明测试越不充分
- 代码覆盖度的种类:
- 函数覆盖:程序中的每个函数是否被调用
- 语句覆盖:程序中的每条语句是否被测试
- 分支覆盖:多条分支是否被测试用例都覆盖
- 路径覆盖:每种可能的分支组合是否都被测试覆盖
- 代码覆盖度不同种类的比较:
- 测试效果:路径覆盖 > 分支覆盖 > 语句覆盖
- 测试难度:路径覆盖 > 分支覆盖 > 语句覆盖
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!