软件分析与设计笔记 14:软件测试:层级、方法与等价类划分

「我们测过了」这句话问题在哪:为什么静态评审和动态测试缺一不可,五个测试层级各自验证什么,白盒与黑盒的差别,以及用等价类划分和边界值把无限输入变成有限用例的方法。

学习#软件测试#质量保证#学习笔记

本文是 《软件分析与设计笔记》 系列的第 14 篇,共 28 篇

这是我在学习软件分析与设计时整理的系列笔记。概念部分是我按自己的理解写的,示例与图示都是自己编、自己画的。理解有偏差的地方欢迎指出。

贯穿案例:片区家电维修派单系统。这一篇讲怎么证明「它真的做到了」—— 重点是 27 篇考试里那道 4 分的测试用例题。

0 「我们测过了」——那测的是什么

交付前总会听到这句话。但接着问三个问题,答案往往就不那么确定了:

  • 测了哪些层级?只测了单个方法能跑,还是连起来也测过?
  • 用了哪些方法?只测了正常输入,还是异常输入也试过?
  • 依据什么数据?随手试的,还是照着约束设计出来的?

放到贯穿案例里试一次。如果你做了「申请配件」这个功能,随口说「测过了」,那下面这些情况测了吗:

  • 数量填 0 会怎样?
  • 数量填 21(上限是 20)会怎样?
  • 工单状态不是「进行中」时提交会怎样?
  • 供应商接口超时了会怎样?
  • 两个工程师同时申请同一个配件(只剩 1 个)会怎样?

这五条里只要有一条没测,「测过了」就不成立。这一篇要把这三件事讲清楚。先建立一个基本认识:测试的目的是发现缺陷(defect),不是证明没有缺陷。测试通过只能说明「这组输入下没出问题」,不能说明系统是对的。

1 为什么只测不够:评审与测试

1.1 评审与测试的分工

有一类缺陷,光靠运行程序是发现不了的:

  • 需求漏写了一个分支 —— 程序按现在的需求跑得很好。本例:「工程师请假了,已派的单怎么办」需求里没写;
  • 设计里两个模块职责重叠 —— 代码能跑,只是以后改起来很痛苦;
  • 文档里的术语前后不一致 —— 甚至没有代码。

对付这类问题要用评审(review)。它和测试的分工是:

评审测试
是否运行程序否,属于静态测试(static testing)是,属于动态测试(dynamic testing)
主要发现需求、设计、文档层面的缺陷实现层面的缺陷
时机越早越好代码可运行之后

评审之所以重要,是因为缺陷发现得越晚,修复成本越高 —— 在需求阶段改一句话,和在测试阶段改一句话,代价差一个数量级。

本例里有个具体的账:如果「工程师请假怎么办」这个缺口没在需求评审时发现,等到上线后才发现,改的不只是一个分支,而是工单状态机要加一个状态、还要能回退 —— 那是两三天的活。

1.2 验证与确认

再补充两个容易混的词:

  • 验证(verification) —— 我们有没有正确地构建产品?(符合规格说明)
  • 确认(validation) —— 我们有没有构建正确的产品?(符合用户需要)

前者是「照着图纸做对了吗」,后者是「图纸本身对吗」。

放到本例:「查询响应 2 秒内完成」测过了 → 这是验证。但客服小林用了之后还是抱怨「找不到要派的单」→ 说明确认没过,因为图纸本身画错了(她需要的不是快,而是筛选)。

2 五个测试层级

测试层级的金字塔与两类测试方法:从下到上是单元测试、集成测试、系统测试、验收测试,越往上覆盖范围越大、发现问题代价越高;下方对比白盒测试与黑盒测试,以及黑盒测试选输入用的等价类划分

2.1 五个层级各自验证什么

层级验证什么谁来测本例的例子
单元测试(unit testing)单个方法或类的行为开发PartUsage.calculateAmount() 算得对不对
集成测试(integration testing)模块之间的接口与协作开发 / 测试申请配件时,库存扣减与出库单创建是不是同一个事务
系统测试(system testing)整个系统的功能与非功能表现测试早高峰 300 人同时在线,派单是否还成功
验收测试(user acceptance testing, UAT)是否满足业务需要用户 / 业务方小林真的能用它派完一天 80 单吗
部署测试(deployment testing)在真实环境里能否正常安装运行运维 / 测试工程师的手机在 4G 弱网下能不能回填

第四行特别值得注意。小林是唯一能判断 UAT 过没过的人,因为她才是那个一天派 80 单的人。如果让没派过单的人去验收,通过与否没有意义。

2.2 金字塔形状说明了什么

金字塔的形状本身就是结论:底层的测试应该最多、最便宜、跑得最勤;上层的测试少而贵。一个健康项目的表现是「大部分缺陷在单元测试阶段就被拦住」。反过来,如果缺陷总是到 UAT 才暴露,那就说明底层测试做得太少。

本例里最容易缺底层的正是金额与时长的计算。配件金额、上门时长、超时判定这三块逻辑如果没单元测试,等到 UAT 才发现算错,改起来要重跑一整套流程。

3 白盒与黑盒

3.1 白盒与黑盒的差别

白盒测试(white box testing) 看内部逻辑:语句有没有都执行到、分支有没有都走到。它适合验证「代码写对了没有」,实施者需要懂代码。

黑盒测试(black box testing) 只看输入和输出,不看实现。它适合从使用者角度验证「行为对不对」,业务方也能参与。

本例里「申请配件」这段逻辑,两种测法的分工很清楚:

测什么
白盒「库存充足」和「库存不足」两个分支都走到了吗?异常处理有代码覆盖吗?
黑盒数量填 0 / 21 / 5,结果对不对?不关心代码怎么写的

两者有一个共同局限:都只能覆盖到你想到的情况。想不出某个输入组合,就永远测不到它。所以测试用例的设计方法比测试工具更重要。

3.2 测试覆盖率的分量

这也解释了测试覆盖率(test coverage)这个指标的分量:它衡量你设计的用例触及了多少代码或需求。但要注意,覆盖率只说明「被执行过」,不等于「被验证正确」—— 覆盖率 100% 也不能保证没有缺陷。

一个具体的反例:某段代码 if (qty > 20) return error; —— 你用 qty=25 测了一次,覆盖率是 100%,但你没测 qty=20(边界,应该通过)。代码跑过了,但不代表跑对了。

4 把无限输入变成有限用例:等价类划分

4.1 它的基本思路

一个输入框能接受的值是无限的,不可能一个个试。等价类划分(equivalence class partitioning) 的思路是:把输入空间划成若干段,每段里任意一个值的效果是一样的 —— 那每段测一个代表值就够了。

4.2 三种划分方式

常见的三种划分方式:

① 按值域划分。 本例「申请数量」允许 1–20,那么:

  • 有效等价类:1–20 内取一个(例如 12);
  • 无效等价类:小于 1(例如 0)、大于 20(例如 21)。

注意有效类里最好取三个:边界值 1、中间值 12、边界值 20。因为 > 写成 >= 这类错误只在边界暴露。

② 按输入个数划分。 例如一张工单至少要有一条配件消耗:

  • 有效:1 条、5 条;
  • 无效:0 条、极大条数(例如 200 条)。

③ 按异常条件(exception condition)划分。 空值、超长字符串、非法字符、并发提交、重复提交。

本例里第 ③ 类最容易被漏掉,但它恰恰最真实:

  • 同一张工单被两个客服同时派给不同的人;
  • 工程师连点两次「提交申请」(网络慢,他以为没反应);
  • 配件编号输入了不存在的型号。

4.3 边界值分析

在值域的边界上还有个配套技巧 —— 边界值分析(boundary value analysis):出错往往发生在边界上(< 写成 <=、0 和 1 的差别),所以在每个等价类的边界上下各取一个值,命中率特别高。

本例「数量 1–20」的完整取法:

值属于期望结果
0下边界外报错
1下边界上通过
12中间通过
20上边界上通过
21上边界外报错

五个值,覆盖三件事:两个边界内外、一个中间值。这比随手填一个 999 有价值得多 —— 因为 999 和 21 是同一个等价类,多测一遍没有新信息。

5 测试准备:四样东西

5.1 四样东西

设计好用例之后,还需要准备:

  • 测试用例(test case) —— 输入、预期输出、前置条件,三者缺一不可;
  • 测试数据(test data) —— 符合用例要求的实际数据。边界数据要专门准备(例如「刚好 20 个」);
  • 测试环境(test environment) —— 与生产尽量接近的环境。环境不一致造成的假问题,非常消耗排查时间;
  • 测试步骤(test procedure) —— 用例的执行顺序。有些用例有先后依赖(先建工单,才能测申请配件)。

第二样本例里有个坑:测「库存不足」需要一个库存真的不足的配件。如果你在测试环境里用的是充足的数据,这条用例根本跑不到那个分支。所以测试数据要专门造「只剩 1 个」的库存。

5.2 功能测试与非功能测试

测试分两类:功能测试(functional testing) 验证系统做什么,非功能测试 验证做得多好(性能、安全、可靠性,见笔记 15)。

6 测试在敏捷里怎么放

6.1 测试成为迭代的一部分

迭代式开发里,测试不再是项目末尾的一个阶段,而是每个迭代的一部分,也就是所谓的敏捷测试周期(Agile testing lifecycle):

  • 单元测试是「完成定义」的一部分(笔记 03)—— 没测试就不算完成;
  • 每次改动后都要跑一遍已有的测试,确认没弄坏别的东西,这叫回归测试(regression testing)。它通常靠自动化来保证;
  • 测试用例的来源变了:以前从需求文档推,现在直接从验收标准推 —— 一条验收标准对应至少一个测试用例。

这个变化带来一个好处:如果一条验收标准写不出对应的测试用例,那多半是这条需求本身没写清楚。

本例里 07 篇那条验收标准「每个时段标注空闲/已排/满档三态」,直接就能翻出三条测试用例(各验一态)+ 一条边界(无排班数据)。翻不出来就说明标准没写清。

本节自测

  1. 验证(verification)和确认(validation)的区别是什么?
  2. 哪些缺陷只能靠评审发现,测试发现不了?举出本例中的一个。
  3. 测试金字塔为什么是「下宽上窄」?如果缺陷老是在 UAT 才暴露,说明什么?
  4. 某字段允许 1–120,用等价类划分和边界值分析,至少要准备哪些测试数据?
  5. 为什么说「写不出测试用例的验收标准,说明需求有问题」?
  6. 本例「申请配件」的数量上限是 20,为什么只测 21 不够,还要测 20?
  7. 本例要测「库存不足」这个分支,测试数据上有什么特别要求?

参考答案

  1. 验证是「照着图纸做对了吗」(符合规格),确认是「图纸本身对吗」(符合用户需要)。本例:「2 秒内完成」测过是验证;但小林仍抱怨「找不到要派的单」说明确认没过(§1.2)。
  2. 需求、设计、文档层面的缺陷。本例中最典型的是「工程师请假了,已派的单怎么办」—— 需求里没写,程序按现有需求跑得很好,测不出问题(§1.1)。
  3. 因为底层测试多、便宜、跑得勤,上层少而贵;健康项目的缺陷大部分在单元层被拦住。若缺陷总在 UAT 暴露,说明底层测试做得太少(§2.2)。
  4. 至少:0(下边界外)、1(下边界上)、60(中间)、120(上边界上)、121(上边界外) —— 五个值,覆盖两个边界内外加一个中间值(§4.3)。
  5. 因为验收标准与测试用例是一对一推导关系。一条标准翻不出用例,说明它本身含糊、没有可观测的结果(§6.1)。
  6. 因为 > 写成 >= 这类错误只在边界暴露。测 21 只能证明「超上限会报错」,测 20 才能证明「正好到上限时应通过」(§4.3、§3.2)。
  7. 必须专门造一个库存不足的配件(例如只剩 1 个)。如果测试数据里库存都充足,这条用例根本跑不到那个分支(§5.1)。

术语表

术语中文一句话解释
verification验证是否按规定构建了产品,对着规格检查
validation确认是否构建了正确的产品,对着用户需要检查
review评审不运行程序、靠人检查发现缺陷的方式
static testing静态测试不执行代码的检查,如评审与静态分析
dynamic testing动态测试实际运行程序来发现缺陷
unit testing单元测试验证单个方法或类的行为
integration testing集成测试验证模块之间的接口与协作
system testing系统测试验证整个系统的功能与非功能表现
user acceptance testing(UAT)验收测试由业务方确认是否满足需要
deployment testing部署测试验证在真实环境中的安装与运行
white box testing白盒测试基于内部逻辑设计的测试
black box testing黑盒测试只看输入输出的测试
equivalence class partitioning等价类划分把输入空间分段,每段取一个代表值
boundary value analysis边界值分析在等价类边界上下取值测试
exception condition异常条件空值、非法输入、并发等异常情形
test case测试用例输入、预期输出与前置条件的组合
test data测试数据执行用例所需的实际数据
test environment测试环境尽量接近生产的运行环境
test procedure测试步骤用例的执行顺序与依赖
functional testing功能测试验证系统做什么
regression testing回归测试改动后重跑既有测试,确认没弄坏别处
test coverage测试覆盖率测试触及代码或需求的比例
defect缺陷与预期不符的系统行为
Agile testing lifecycle敏捷测试周期测试融入每个迭代而非独立阶段

© 2026 HuanYu Blog

记录技术,也记录思考