概述
软件测试中,根据测试人员对被测软件内部结构的了解程度,可以将测试方法分为黑盒测试、白盒测试和灰盒测试三类。每种测试方法都有其特定的应用场景和优缺点。
黑盒测试 vs 白盒测试
| 特征 | 黑盒测试 | 白盒测试 |
|---|---|---|
| 测试角度 | 用户视角,关注功能 | 开发者视角,关注结构 |
| 内部结构 | 不需要了解 | 需要深入了解 |
| 测试依据 | 需求规格说明 | 源代码和设计文档 |
| 测试目标 | 功能正确性 | 逻辑正确性和覆盖率 |
| 适用阶段 | 系统测试、验收测试 | 单元测试、集成测试 |
| 测试人员 | 测试工程师、用户 | 开发人员、白盒测试工程师 |
黑盒测试
黑盒测试(Black Box Testing)是一种基于软件外部功能的测试方法,测试人员不需要了解软件的内部结构和实现细节,只需要根据需求规格说明来验证软件的功能是否符合预期。
等价类划分法
等价类划分法是将输入域划分为若干个等价类,从每个等价类中选取少数代表性数据作为测试用例。
原理
- 有效等价类:符合程序规格说明的输入数据集合
- 无效等价类:不符合程序规格说明的输入数据集合
设计步骤
- 确定输入条件
- 划分有效等价类和无效等价类
- 建立等价类表
- 为每个等价类设计测试用例
示例:用户注册密码验证
需求:密码长度6-20位,必须包含字母和数字
等价类划分:
有效等价类:
- EC1: 长度6-20位,包含字母和数字
无效等价类:
- EC2: 长度小于6位
- EC3: 长度大于20位
- EC4: 只包含字母
- EC5: 只包含数字
- EC6: 包含特殊字符
- EC7: 空密码
测试用例:
- TC1: "abc123" (EC1)
- TC2: "ab12" (EC2)
- TC3: "abcdefghij1234567890123" (EC3)
- TC4: "abcdef" (EC4)
- TC5: "123456" (EC5)
- TC6: "abc@123" (EC6)
- TC7: "" (EC7)
优缺点
- 优点:减少测试用例数量,提高测试效率
- 缺点:可能遗漏边界值错误
边界值分析法
边界值分析法是对等价类划分法的补充,专注于测试输入域的边界值。
原理
大量错误发生在输入域的边界上,而不是输入域的内部。
边界值选取原则
- 如果输入条件规定了值的范围,选取边界值和临近边界值
- 如果输入条件规定了值的个数,选取最小个数、最大个数等
示例:年龄输入验证
需求:年龄范围18-65岁
边界值:
- 下边界:17, 18, 19
- 上边界:64, 65, 66
测试用例:
- TC1: age = 17 (无效)
- TC2: age = 18 (有效)
- TC3: age = 19 (有效)
- TC4: age = 40 (有效,中间值)
- TC5: age = 64 (有效)
- TC6: age = 65 (有效)
- TC7: age = 66 (无效)
因果图设计法
因果图设计法用于描述输入条件的组合与输出结果之间的因果关系。
基本符号
- 恒等:若原因出现,则结果出现
- 非:若原因出现,则结果不出现
- 或:若干个原因中有一个出现,则结果出现
- 与:若干个原因都出现,则结果出现
设计步骤
- 分析输入条件和输出结果
- 画出因果图
- 转换为判定表
- 设计测试用例
示例:文件操作权限
输入条件:
- C1: 用户是文件所有者
- C2: 用户有读权限
- C3: 用户有写权限
输出结果:
- E1: 可以读取文件
- E2: 可以修改文件
因果关系:
- E1 = C1 OR C2
- E2 = C1 OR C3
判定表:
| C1 | C2 | C3 | E1 | E2 |
|----|----|----|----|
| T | - | - | T | T |
| F | T | F | T | F |
| F | F | T | F | T |
| F | F | F | F | F |
正交实验设计法
正交实验设计法使用正交表来设计测试用例,用最少的测试用例覆盖最多的输入组合。
原理
利用正交表的均衡分散性和整齐可比性,从大量的测试数据中挑选适量的、有代表性的点进行测试。
示例:Web应用测试
因素和水平:
- 浏览器(A): Chrome, Firefox, Safari
- 操作系统(B): Windows, Mac, Linux
- 分辨率(C): 1920x1080, 1366x768, 1024x768
使用L9(3^4)正交表:
| 测试用例 | 浏览器 | 操作系统 | 分辨率 |
|----------|--------|----------|--------|
| TC1 | Chrome | Windows | 1920x1080 |
| TC2 | Chrome | Mac | 1366x768 |
| TC3 | Chrome | Linux | 1024x768 |
| TC4 | Firefox | Windows | 1366x768 |
| TC5 | Firefox | Mac | 1024x768 |
| TC6 | Firefox | Linux | 1920x1080 |
| TC7 | Safari | Windows | 1024x768 |
| TC8 | Safari | Mac | 1920x1080 |
| TC9 | Safari | Linux | 1366x768 |
错误推测法
错误推测法基于测试人员的经验和直觉,推测程序中可能存在的错误。
常见错误类型
- 输入数据为0、空值、null
- 输入数据超出预期范围
- 并发访问导致的数据不一致
- 资源耗尽(内存、磁盘空间)
- 网络异常、超时
场景法
场景法通过模拟用户的实际使用场景来设计测试用例。
设计步骤
- 识别基本流和备选流
- 构建场景
- 设计测试用例
示例:ATM取款场景
基本流:
1. 插入银行卡
2. 输入密码
3. 选择取款
4. 输入金额
5. 取钱
6. 退卡
备选流:
- 密码错误
- 余额不足
- ATM现金不足
- 超过单日限额
- 网络故障
测试场景:
- 场景1:正常取款流程
- 场景2:密码错误3次锁卡
- 场景3:余额不足提示
- 场景4:取款金额超限
白盒测试
白盒测试(White Box Testing)是基于程序内部结构的测试方法,测试人员需要了解程序的内部逻辑结构,检查程序的内部运行是否符合设计规格说明的要求。
逻辑覆盖法
逻辑覆盖是白盒测试的主要方法,通过测试用例的执行,检查程序中的逻辑路径是否被覆盖。
语句覆盖(Statement Coverage)
确保程序中的每个语句至少被执行一次。
示例代码:
public int calculate(int a, int b) {
int result = 0; // 语句1
if (a > 0) { // 语句2
result = a + b; // 语句3
}
if (b > 0) { // 语句4
result = result * 2; // 语句5
}
return result; // 语句6
}
// 测试用例:
// TC1: calculate(1, 1) - 覆盖所有语句
// 语句覆盖率:100%
判定覆盖(Decision Coverage)
也称为分支覆盖,确保每个判定的真假分支都至少执行一次。
示例:
// 使用上面的代码
// 测试用例:
// TC1: calculate(1, 1) - a>0为真,b>0为真
// TC2: calculate(-1, -1) - a>0为假,b>0为假
// 判定覆盖率:100%
条件覆盖(Condition Coverage)
确保每个判定中的每个条件都取到真值和假值。
示例代码:
public boolean validate(int x, int y) {
if (x > 0 && y > 0) { // 条件1: x>0, 条件2: y>0
return true;
}
return false;
}
// 测试用例:
// TC1: validate(1, -1) - x>0为真,y>0为假
// TC2: validate(-1, 1) - x>0为假,y>0为真
// 条件覆盖率:100%
判定-条件覆盖(Decision-Condition Coverage)
同时满足判定覆盖和条件覆盖。
示例:
// 使用上面validate函数
// 测试用例:
// TC1: validate(1, 1) - x>0为真,y>0为真,判定为真
// TC2: validate(1, -1) - x>0为真,y>0为假,判定为假
// TC3: validate(-1, 1) - x>0为假,y>0为真,判定为假
// 判定-条件覆盖率:100%
条件组合覆盖(Multiple Condition Coverage)
覆盖每个判定中所有条件的所有可能组合。
示例:
// 使用上面validate函数
// 所有条件组合:
// TC1: validate(1, 1) - (T, T)
// TC2: validate(1, -1) - (T, F)
// TC3: validate(-1, 1) - (F, T)
// TC4: validate(-1, -1) - (F, F)
// 条件组合覆盖率:100%
路径覆盖(Path Coverage)
覆盖程序中所有可能的执行路径。
覆盖率强度比较:
路径覆盖 > 条件组合覆盖 > 判定-条件覆盖 > 判定覆盖 > 语句覆盖
基本路径测试法
基本路径测试法是Tom McCabe提出的一种白盒测试方法。
步骤
- 画出程序的控制流图
- 计算环路复杂度
- 确定独立路径集合
- 设计测试用例覆盖每条独立路径
环路复杂度计算
V(G) = E - N + 2
其中:E = 边数,N = 节点数
或者:
V(G) = P + 1
其中:P = 判定节点数
循环测试
针对程序中的循环结构进行专门测试。
简单循环测试
for (int i = 0; i < n; i++) {
// 循环体
}
测试用例:
- 跳过循环(n = 0)
- 只执行一次(n = 1)
- 执行两次(n = 2)
- 执行m次(m < n的典型值)
- 执行n-1次
- 执行n次
- 执行n+1次(如果可能)
嵌套循环测试
- 从最内层循环开始测试
- 逐步向外层扩展
- 保持外层循环为最小值
插桩技术
插桩是在程序中插入探测点,用于收集程序运行时的信息。
源代码插桩
public void method() {
System.out.println("[TRACE] Entering method"); // 插桩代码
// 原始代码
int result = calculate();
System.out.println("[TRACE] Result: " + result); // 插桩代码
System.out.println("[TRACE] Exiting method"); // 插桩代码
}
字节码插桩
使用工具如ASM、Javassist在字节码级别插入代码。
灰盒测试
灰盒测试(Gray Box Testing)结合了黑盒测试和白盒测试的特点,测试人员对系统内部有部分了解。
特点
- 基于有限的内部知识设计测试用例
- 主要用于集成测试
- 关注输入输出的同时考虑内部状态
应用场景
- Web应用测试(了解架构但不深入代码)
- 数据库测试(了解表结构但不关注实现)
- API测试(了解接口定义但不关注内部逻辑)
测试用例设计实例
登录功能测试用例设计
黑盒测试用例
等价类划分:
1. 用户名:有效用户名、无效用户名、空用户名
2. 密码:正确密码、错误密码、空密码
边界值分析:
1. 用户名长度:0、1、20、21字符
2. 密码长度:0、6、20、21字符
测试用例:
| ID | 用户名 | 密码 | 预期结果 |
|----|--------|------|----------|
| TC1 | valid_user | correct_pwd | 登录成功 |
| TC2 | valid_user | wrong_pwd | 密码错误 |
| TC3 | invalid_user | any_pwd | 用户不存在 |
| TC4 | "" | any_pwd | 请输入用户名 |
| TC5 | valid_user | "" | 请输入密码 |
| TC6 | a | 123456 | 用户名过短 |
| TC7 | user_name_with_21_chars | pwd | 用户名过长 |
白盒测试用例
public boolean login(String username, String password) {
if (username == null || username.isEmpty()) { // 分支1
return false;
}
if (password == null || password.length() < 6) { // 分支2
return false;
}
User user = userDao.findByUsername(username); // 分支3
if (user == null) {
return false;
}
return user.getPassword().equals(password); // 分支4
}
// 路径覆盖测试用例:
// 路径1: username为空 -> false
// 路径2: password长度不足 -> false
// 路径3: 用户不存在 -> false
// 路径4: 密码不匹配 -> false
// 路径5: 登录成功 -> true
测试工具介绍
黑盒测试工具
功能测试工具
- Selenium:Web应用自动化测试
- Appium:移动应用自动化测试
- Postman:API测试工具
- JMeter:性能测试工具
示例:Selenium测试脚本
from selenium import webdriver
from selenium.webdriver.common.by import By
def test_login():
driver = webdriver.Chrome()
driver.get("http://example.com/login")
# 输入用户名密码
driver.find_element(By.ID, "username").send_keys("testuser")
driver.find_element(By.ID, "password").send_keys("testpass")
# 点击登录
driver.find_element(By.ID, "login-btn").click()
# 验证登录成功
assert "Dashboard" in driver.title
driver.quit()
白盒测试工具
单元测试框架
- JUnit(Java)
- TestNG(Java)
- pytest(Python)
- Jest(JavaScript)
代码覆盖率工具
- JaCoCo(Java)
- Coverage.py(Python)
- Istanbul(JavaScript)
示例:JUnit + JaCoCo
@Test
public void testCalculate() {
Calculator calc = new Calculator();
// 测试正数
assertEquals(5, calc.add(2, 3));
// 测试负数
assertEquals(-1, calc.add(-3, 2));
// 测试零
assertEquals(0, calc.add(0, 0));
}
// Maven配置JaCoCo
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
静态分析工具
- SonarQube:代码质量分析
- FindBugs/SpotBugs:Java代码缺陷检测
- ESLint:JavaScript代码检查
- Pylint:Python代码检查
性能测试
性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。
性能测试指标
1.响应时间
响应时间Response Time指系统对用户请求作出响应所需要的时间。 这个时间是指用户从软件客户端发出请求到用户接收到返回数据的整个过程所需要的时间,包括各种中间件如服务器数据库等的处理时间。
2.吞吐量
吞吐量Throughput是指单位时间内系统能够完成的工作量,它衡量的是软件系统服务器的处理能力。
吞吐量的度量单位可以是请求数/秒页面数/秒访问人数/天处理业务数/小时等。
3.并发用户数
并发用户数是指同一时间请求和访问的用户数量。
4.TPS(Transaction Per Second)
TPS是指系统每秒钟能够处理的事务和交易的数量,它是衡量系统处理能力的重要指标。
5.点击率(Hit Per Second)
点击率是指用户每秒向Web服务器提交的HTTP请求数,这个指标是Web应用特有的一个性能指标,通过点击率可以评估用户产生的负载量,并且可以判断系统是否稳定。点击率只是一个参考指标,帮助衡量Web服务器的性能。
6.资源利用率
资源利用率是指软件对系统资源的使用情况,包括CPU利用率内存利用率磁盘利用率等,资源利用率是分析软件性能瓶颈的重要参数。
性能测试种类
1.负载测试(Load Testing)
负载测试是指逐步增加系统负载,测试系统性能的变化,并最终确定在满足系统性能指标的情况下,系统所能够承受的最大负载量。
2.压力测试(Stress Testing)
压力测试也叫强度测试,它是指逐步给系统增加压力,测试系统的性能变化,使系统某些资源达到饱和或系统崩溃的边缘,从而确定系统所能承受的最大压力。
压力测试可以揭露那些只有在高负载条件下才会出现的Bug,如同步问题内存泄露等。
压力测试与负载测试: 负载测试是在保持性能指标要求的前提下系统能够承受的最大负载,而压力测试则是使系统性能达到极限的状态。
3.并发测试(Concurrency Testing)
并发测试是指通过模拟用户并发访问,测试多用户并发访问同一个应用同一个模块或者数据记录时是否存在死锁或其他性能问题。
并发测试一般没有标准,只是测试并发时会不会出现意外情况,几乎所有的性能测试都会涉及到一些并发测试,例如多个用户同时访问某一条件数据,多个用户同时在更新数据,那么数据库可能就会出现访问错误写入错误等异常情况。
4.配置测试(Configuration Testing)
配置测试是指调整软件系统的软硬件环境,测试各种环境对系统性能的影响,从而找到系统各项资源的最优分配原则。 配置测试不改变代码,只改变软硬件配置,例如安装版本更高的数据库配置性能更好的CPU内存等,通过更改外部配置来提高软件的性能。
5.可靠性测试(Reliability Testing)
可靠性测试是指给系统加载一定的业务压力,使其持续运行一段时间如7*24h,测试系统在这种条件下是否能够稳定运行。
6.容量测试(Volume Testing)
容量测试是指在一定的软硬件及网络环境下,测试系统所能支持的最大用户数最大存储量等。
容量测试通常与数据库系统资源如CPU内存磁盘等有关,用于规划将来需求增长如用户增长业务量增加等时,对数据库和系统资源的优化。
性能测试流程

1.分析性能测试需求
在性能测试需求分析阶段,测试人员需要收集有关项目的各种资料,并与开发人员进行沟通,对整个项目有一定的了解,针对需要进行性能测试的部分进行分析,确定测试目标。
2.制定性能测试计划
● 确定测试环境:包括物理环境生产环境测试团队可利用的工具和资源等。 ● 确定性能验收标准:确定响应时间吞吐量和系统资源CPU内存等利用总目标和限制。 ● 设计测试场景:对产品业务用户使用场景进行分析,设计符合用户使用习惯的场景,整理出一个业务场景表,为编写测试脚本提供依据。 ● 准备测试数据:性能测试是模拟现实的使用场景,例如模拟用户高并发,则需要准备用户数量工作时间测试时长等数据。
3.设计测试用例
性能测试用例是根据测试场景为测试准备数据,例如模拟用户高并发,可以分别设计100用户并发数量1000用户并发数量等,此外还要考虑用户活跃时间访问频率场景交互等各种情况。测试人员可以根据测试计划中的业务场景表设计出足够的测试用例以达到最大的测试覆盖。
4.编写性能测试脚本
● 正确选择协议。 ● 根据工具的支持情况和测试人员熟悉程度选取脚本语言。 ● 编写测试脚本时,要遵循代码编写规范,保证代码的质量。 ● 做好脚本的维护管理工作。
5.测试执行及监控
● 性能指标:本次性能测试要测试的性能指标的变化。 ● 资源占用与释放情况:CPU内存磁盘网络等使用情况。性能测试停止后,各项资源是否能正常释放以供后续业务使用。 ● 警告信息:一般软件系统在出现问题时会发出警告信息,当有警告信息时,测试人员要及时查看。 ● 日志检查:经常分析系统日志,包括操作系统数据库等日志。
6.运行结果分析
性能测试完成之后,测试人员需要收集整理测试数据并对数据进行分析,将测试数据与客户要求的性能指标进行对比,若不满足客户的性能要求,需要进行性能调优然后重新测试,直到产品性能满足客户需求。
7.性能测试总结
性能测试完成之后需要编写性能测试报告,阐述性能测试的目标性能测试环境性能测试用例与脚本使用情况性能测试结果及性能测试过程中遇到的问题和解决办法等。软件产品不会只进行一次性能测试,因此性能测试报告需要备案保存,作为下次性能测试的参考。
LoadRunner使用
LoadRunner是HP公司的一款商业性能测试工具,包含三个主要组件:
1.VuGen(Virtual User Generator)
LoadRunner是通过多个虚拟用户在系统中同时工作或访问系统的环境来进行性能测试的,虚拟用户进行的操作通常被记录在虚拟用户脚本中,而VuGen就是用于创建虚拟用户脚本的工具,因此它也称为虚拟用户脚本生成器。
在创建脚本时,VuGen会生成多个函数用于记录虚拟用户所执行的操作,并将这些函数插入到VuGen编辑器生成基本的虚拟用户脚本,这个创建脚本的过程也叫做录制脚本。
2.Controller
Controller用于创建和控制LoadRunner场景,场景负责定义每次测试中发生的事件,包括模拟的用户数用户执行的操作以及测试要监控的性能指标等。
3.Analysis
Analysis是LoadRunner的数据分析工具,它可以收集性能测试中的各种数据,对其进行分析并生成图表和报告供测试人员查看。
LoadRunner脚本示例
// 登录事务
lr_start_transaction("Login");
// 发送HTTP请求
web_url("login_page",
"URL=http://example.com/login",
"Resource=0",
"RecContentType=text/html",
LAST);
// 提交登录表单
web_submit_data("login_submit",
"Action=http://example.com/login/submit",
"Method=POST",
"RecContentType=text/html",
ITEMDATA,
"Name=username", "Value={username}", ENDITEM,
"Name=password", "Value={password}", ENDITEM,
LAST);
// 检查登录是否成功
if (web_reg_find("Text=Welcome", LAST) == LR_PASS) {
lr_end_transaction("Login", LR_PASS);
} else {
lr_end_transaction("Login", LR_FAIL);
}
// 思考时间
lr_think_time(5);
最佳实践
测试策略选择
不同测试阶段的方法选择
-
单元测试阶段:以白盒测试为主
- 使用语句覆盖、分支覆盖等方法
- 配合单元测试框架(JUnit、pytest等)
-
集成测试阶段:灰盒测试为主
- 了解模块接口但不深入内部实现
- 重点测试模块间的交互
-
系统测试阶段:黑盒测试为主
- 从用户角度验证功能
- 使用等价类、边界值等方法
-
验收测试阶段:黑盒测试
- 基于用户需求和使用场景
- 重点关注业务流程
测试用例设计原则
- 完整性:覆盖所有功能需求
- 准确性:测试步骤清晰,预期结果明确
- 可重复性:任何人都能执行并得到相同结果
- 可追溯性:与需求一一对应
- 经济性:用最少的用例达到最大的覆盖
测试覆盖率目标
代码覆盖率建议:
- 单元测试:80%以上
- 核心模块:90%以上
- 工具类:接近100%
- UI层:60-70%
功能覆盖率:
- 核心功能:100%
- 主要功能:95%以上
- 辅助功能:80%以上
测试自动化建议
-
自动化测试金字塔
/\ UI测试(10%) / \ / \ 集成测试(20%) / \ /________\ 单元测试(70%) -
自动化测试ROI评估
- 执行频率高的测试优先自动化
- 稳定的功能优先自动化
- 数据驱动的测试适合自动化
常见问题
Q1: 如何选择黑盒测试还是白盒测试?
答:选择测试方法需要考虑以下因素:
- 测试目标:功能验证选黑盒,代码质量选白盒
- 测试阶段:早期用白盒,后期用黑盒
- 资源限制:白盒需要更多技术能力
- 时间限制:黑盒测试设计相对快速
最佳实践是结合使用,形成互补。
Q2: 如何提高测试覆盖率?
答:
- 使用覆盖率工具:实时监控覆盖情况
- 增量覆盖:新代码必须有对应测试
- 定期审查:识别未覆盖的代码
- 重构测试:优化测试用例结构
- Mock技术:模拟难以测试的场景
Q3: 性能测试什么时候开始?
答:
- 早期介入:在架构设计阶段就要考虑性能
- 持续测试:每个迭代都进行基准测试
- 正式测试:系统功能稳定后进行完整性能测试
- 上线前测试:模拟生产环境进行最终验证
Q4: 如何设计高质量的测试用例?
答:
- 理解需求:深入理解业务逻辑
- 用户视角:从实际使用场景出发
- 风险导向:重点测试高风险区域
- 数据驱动:使用真实或接近真实的数据
- 持续优化:根据缺陷分析优化用例
Q5: 测试环境与生产环境的差异如何处理?
答:
- 环境隔离:建立独立的测试环境
- 配置管理:使用配置文件管理差异
- 数据脱敏:使用脱敏后的生产数据
- 模拟服务:使用Mock服务模拟外部依赖
- 容器化:使用Docker保证环境一致性
相关文章
- 软件测试基础知识
- 自动化测试框架设计
- 测试用例管理最佳实践
- 性能测试工具对比
- 持续集成中的测试策略