软件工程这门课,软件测试这一章基本是所有人绕不过去的一道坎。它不像需求分析、概要设计那种偏概念的章节,背一背就能混过去——软件测试的习题,尤其是白盒覆盖、等价类划分、边界值分析、控制流图与环路复杂度这几类,是真的要动手算、动笔画,算错一步整道题的分就没了。我前后整理过不少软件工程习题和配套的参考答案,也和准备软件测试实习面试、参加全国大学生软件测试大赛的同学聊过,发现大家在这一章卡壳的地方高度集中:概念分不清、覆盖标准搞混、测试用例凭感觉写、环路复杂度算错。这篇就把软件测试章节的习题体系整个拆开,从考点分布、易混概念、典型题的完整解题过程,到参考答案该怎么用、错题怎么复盘,尽量讲透。不管你是软件工程导论期末抱佛脚,还是准备软件测试岗的笔试八股,都能直接拿去用。
1. 软件测试章节的考点地图:先搞清楚会考哪些题
1.1 软件工程教材里这一章的固定考点清单
不管你用的是吕云翔版、张海藩版还是其他主流教材,软件测试这一章的知识骨架其实非常固定,翻来覆去就这五块内容。第一块是测试基础概念,包括软件测试的定义、目的、基本原则,还有测试与调试的区别,这类属于必须死记的保分点。第二块是测试方法分类,黑盒、白盒、灰盒,各自的适用场景、优缺点、典型方法归属。第三块是测试级别,单元测试、集成测试、系统测试、验收测试,以及配套的V模型、W模型和增量式集成策略(自顶向下、自底向上、三明治)。第四块是测试用例设计方法,这也是出大题的地方:黑盒这边有等价类划分、边界值分析、判定表、因果图、错误推测、场景法;白盒这边有逻辑覆盖的六种标准、基本路径测试、控制流图、环路复杂度。第五块是面向对象测试、测试管理和测试策略这类偏理论的内容,一般以简答或选择形式出现。
1.2 不同题型的出题概率与分值分布
我把常见的软件测试习题按题型、出现频率和分值整理成一张表,你复习的时候可以按这张表分配精力,别在低分题上耗太久,也别把拉分的大题给漏了。
| 题型 | 典型问法 | 出现频率 | 平均分值 | 难度 |
|---|---|---|---|---|
| 选择题/填空 | 概念辨析、原则判断 | 高 | 2-4分 | 低 |
| 简答题 | 简述测试原则、测试与调试区别 | 高 | 5-8分 | 低中 |
| 用例设计题 | 给定功能,用等价类/边界值设计用例 | 中高 | 10-15分 | 中 |
| 白盒覆盖题 | 给定程序段,设计各覆盖标准的用例 | 高 | 12-20分 | 高 |
| 控制流图与环路复杂度 | 画控制流图、计算V(G) | 中 | 8-12分 | 中高 |
| 判定表/因果图 | 给定业务逻辑,画判定表并生成用例 | 中 | 8-12分 | 中 |
从这张表能看出来一件事:白盒覆盖和用例设计是真正的拉分点,占的分值大、难度高,是决定你能不能拿高分的关键;而选择和简答是保分点,答不上来纯粹是没背。复习策略上,简答和选择保证不丢分,然后用大量时间练白盒覆盖和黑盒用例设计这两块。
1.3 课程习题和软件测试面试八股的重合地带
很多人以为课程习题只是为考试服务,其实软件测试这一章的习题和面试八股重合度极高。面试里高频出现的"黑盒和白盒的区别""等价类划分和边界值分析怎么用""测试流程是什么""测试用例包含哪些要素""单元测试和集成测试的区别""缺陷的生命周期",几乎全部能从教材这一章直接找到答案。也就是说,你把这一章的习题吃透,等于顺手把软件测试岗的笔试基础题也过了一遍。我自己当年就是因为把白盒覆盖的几道经典题推得滚瓜烂熟,面试时被问到"语句覆盖和判定覆盖的区别"时张口就来,细节都能说清楚,这种熟练度是临时突击背概念给不了的。
2. 先把易混概念理清楚,否则做题全是坑
2.1 测试与调试:名字像,本质完全不同
这是简答题的常客,也是很多人答得很单薄的地方。大部分人只会写"测试是发现错误,调试是改正错误",这么答只能拿到一半的分。要按维度展开才能拿满,我习惯用一张对比表来记:
| 维度 | 软件测试 | 程序调试 |
|---|---|---|
| 目的 | 发现错误 | 定位并修正错误 |
| 参与者 | 测试人员或独立测试团队 | 一般是开发人员自己 |
| 时间点 | 贯穿整个软件生命周期 | 通常在测试发现缺陷之后 |
| 方法 | 有计划、有设计好的测试用例 | 靠经验、推理、逐层排查 |
| 结果 | 输出缺陷清单 | 输出修正后的程序 |
| 出发点 | 从"证明程序有错"出发 | 从"修复已发现的错误"出发 |
一句话概括二者的关系:测试是"找出问题",调试是"解决问题"。测试可以发现错误,但发现错误之后的定位和修正属于调试的范畴。答题时把这几个维度分点写出来,阅卷老师一眼就能看出你是真懂了。
2.2 验证与确认:一个问"做得对不对",一个问"做的是不是对的东西"
Verification(验证)和Validation(确认)这对概念,英文教材里问得特别多,中文教材也经常考。记忆口诀很简单:验证看过程,确认看结果;验证查规范,确认查需求。
具体来说,验证回答的是"我们是不是正确地构建了产品"(Are we building the product right),检查开发过程是否符合规范、设计是否和需求一致,手段包括评审、静态分析、走查。确认回答的是"我们构建的是不是正确的产品"(Are we building the right product),检查最终产品是否符合用户真实需求,手段主要是系统测试和验收测试。我见过很多同学把这两个名字对调着写,其实只要抓住"验证对内部规范、确认对用户需求"这条线,就不会混。
2.3 错误、缺陷、故障、失效四兄弟
这一组概念在各版本教材里用词不太统一,是最容易翻车的地方。一般的关系链是这样的:人犯了错误(Error/Mistake),这个错误被写进代码或文档就形成了缺陷(Defect/Bug),缺陷在被触发时表现为系统内部的异常状态即故障(Fault),故障在运行中向外部暴露出来就成了失效(Failure)。
链路记住:人犯错→引入缺陷→运行触发故障→对外表现为失效。要特别提醒一点,国内不同教材对Fault和Defect的翻译不完全一样,有的把Fault译成"故障",有的译成"缺陷",考试时一定以你手上那本教材为准。这个坑我踩过,模拟考和期末考的参考答案对同一个词解释不同,白丢了几分。
2.4 黑盒、白盒、灰盒的选择逻辑
这三种方法的选择不是凭喜好,而是看你能不能看到内部结构、测到什么粒度。整理成表更清楚:
| 类型 | 是否看内部结构 | 测试依据 | 适用阶段 | 常见方法 |
|---|---|---|---|---|
| 黑盒 | 不看 | 需求/规格说明 | 系统、验收 | 等价类、边界值、判定表、因果图、场景法 |
| 白盒 | 看 | 代码/内部逻辑 | 单元 | 逻辑覆盖、基本路径、循环测试 |
| 灰盒 | 部分看 | 结构+功能 | 集成 | 接口测试、数据库测试 |
选择逻辑一句话:有源码且要测细粒度逻辑就用白盒,只关注功能表现就用黑盒,需要兼顾两者就用灰盒。实际项目中这三者不是互斥的,而是互补的——单元阶段以白盒为主,系统阶段以黑盒为主,集成阶段往往灰盒更好用。
3. 典型习题的完整解题过程
3.1 白盒覆盖习题:六种覆盖标准一次算清
这是最经典的软件测试大题。给定一段带两个判定的程序,要求分别用语句覆盖、判定覆盖、条件覆盖、判定/条件覆盖、条件组合覆盖、路径覆盖设计测试用例,并说明最少用例数。我拿一道教材高频题完整走一遍。
程序段如下(伪代码,实际考试里可能是C或Pascal):
void sample(int a, int b, float x) { if (a > 1 && b == 0) { x = x / a; } if (a == 2 || x > 1) { x = x + 1; } }第一步,拆条件和判定。判定P1 = (a>1) && (b==0),内部含两个条件:c1为a>1,c2为b==0。判定P2 = (a==2) || (x>1),内部含两个条件:c3为a==2,c4为x>1。这里有个极其关键的细节:第一个if可能会修改x的值,所以设计第二个判定的用例时,x要用它当时的值来判断,而不是初始值。很多人就死在这个细节上。
语句覆盖:让两条赋值语句都执行到。取a=2, b=0, x=4,P1 = (2>1)&&(0==0)为真,执行x=4/2=2;此时P2 = (2==2)||... 为真,执行x=2+1=3。两条语句都跑到了,最少1个用例。
判定覆盖:P1真假、P2真假都要取到。用例1取a=2, b=0, x=4,P1真、P2真;用例2取a=1, b=1, x=1,P1假(a>1不成立)、P2假(a==2不成立且x>1不成立)。两个用例搞定。
条件覆盖:c1、c2、c3、c4每个条件都取真取假。还是上面两个用例:用例1里c1真、c2真、c3真、c4真;用例2里a=1、b=1、x=1,c1假、c2假、c3假、c4假。四个条件都取到了真和假,也是两个用例。
判定/条件覆盖:同时满足判定覆盖和条件覆盖,上面两个用例正好同时满足,用例数不变。
条件组合覆盖:这个要用心。P1的条件组合有TT、TF、FT、FF四种,P2的条件组合也有四种。设计如下:
| 用例 | a | b | x初值 | c1 | c2 | c3 | c4 | 说明 |
|---|---|---|---|---|---|---|---|---|
| 1 | 2 | 0 | 4 | 真 | 真 | 真 | 真 | P1的TT,P2的TT |
| 2 | 2 | 1 | 1 | 真 | 假 | 真 | 假 | P1的TF,P2的TF |
| 3 | 1 | 0 | 3 | 假 | 真 | 假 | 真 | P1的FT,P2的FT |
| 4 | 1 | 1 | 1 | 假 | 假 | 假 | 假 | P1的FF,P2的FF |
用例3里要注意,a=1导致P1为假,x保持3不变,进入P2时c3为假(a不等于2)、c4为真(3>1),对应P2的FT组合,没问题。四个用例覆盖了全部条件组合。
路径覆盖:两个判定,控制流图理论上有四条路径。TT取a=2, b=0, x=4;TF要P1真且P2假,取a=3, b=0, x=2,P1真后x变为2/3约0.67,P2中a不等于2且0.67不大于1,为假,正确;FT取a=1, b=0, x=3,P1假、P2真;FF取a=1, b=1, x=1,P1假、P2假。四个用例覆盖全部路径。
注意:条件组合覆盖和路径覆盖不是一回事,条件组合关注每个判定的条件取值组合,路径覆盖关注整体执行路径。有些题会问"哪个覆盖标准最强",答案是路径覆盖通常最强,但条件组合覆盖在某些场景下能覆盖到路径覆盖覆盖不到的组合。
3.2 黑盒测试习题:等价类划分与边界值分析的组合拳
黑盒用例设计题最经典的载体是三角形判定、日期计算、成绩等级转换、登录校验这几类。我以"三角形判定"为例讲完整流程。题目通常是:输入三条边a、b、c,判断是等边、等腰、普通三角形还是非三角形,用等价类划分和边界值分析设计测试用例。
先做等价类划分。有效等价类要考虑:能构成三角形(任意两边之和大于第三边)、且分为等边(三边相等)、等腰(两边相等)、普通三角形三类。无效等价类要考虑:某边为0或负数、两边之和等于第三边(退化成一条线)、两边之和小于第三边。划分时一个原则是"同一类里任一数据对结果的作用相同",这样每类取一个代表就够了。
再做边界值分析。三角形的边界在于"两边之和等于第三边"这个临界点,所以要在a+b=c的附近取值,包括a+b=c-1、a+b=c、a+b=c+1三种情况,以及对边长最小值(正数最小一般取1)和典型有效值的考察。
合起来设计用例,用表格呈现:
| 编号 | a | b | c | 预期结果 | 覆盖内容 |
|---|---|---|---|---|---|
| 1 | 5 | 5 | 5 | 等边 | 有效,等边 |
| 2 | 5 | 5 | 6 | 等腰 | 有效,等腰 |
| 3 | 4 | 5 | 6 | 普通三角形 | 有效,普通 |
| 4 | 3 | 4 | 7 | 非三角形 | 无效,两边和等于第三边 |
| 5 | 1 | 2 | 10 | 非三角形 | 无效,两边和小于第三边 |
| 6 | 0 | 4 | 5 | 非三角形 | 无效,边的非法值 |
| 7 | 5 | 5 | 9 | 非三角形(退化/等腰临界) | 边界,等腰加退化 |
提示:等价类和边界值经常配合使用——等价类负责"每类一个代表",边界值负责"在临界点附近多取几个"。出题人让你"用两种方法组合设计"时,别只写等价类,边界附近必须补用例。
3.3 控制流图与环路复杂度:画图加计算
这类题一般给一段代码,让你画出控制流图、计算环路复杂度、找出独立路径。控制流图的画法:顺序结构画成连续的节点,每个判定节点分出真和假两条边,汇合处再并成一个节点。复合条件(如A && B)在画控制流图时通常当成一个判定节点处理,但做条件组合覆盖时要拆开,这个区分前面已经踩过,这里再强调一遍。
环路复杂度V(G)有三种等价算法:一是V(G) = E - N + 2,其中E是边数、N是节点数;二是V(G) = P + 1,P是判定节点(谓词节点)的个数;三是V(G) = 区域数(含外部无界区域)。三种方法算出来应该完全一致,考试时我习惯用第二种快速估一下,再用第一种精确核对。
得到V(G)之后,独立路径数就等于V(G),然后依据这些路径设计测试用例。基本路径测试的核心思想就是让测试用例覆盖所有独立路径,保证每条边至少走一次。常见考点还包括"环形复杂度代表程序独立路径的条数,也代表测试需要的最少用例数"这句话的判断。
3.4 判定表与因果图习题的处理套路
判定表题的套路非常固定,按步骤走不会出错。先列条件桩(所有输入条件),再列动作桩(所有可能的输出动作),然后确定规则数——如果n个条件每个取真取假,理论上是2^n条规则。接着逐条填条件项和动作项,最后对相同动作的规则做合并简化。
| 步骤 | 操作内容 | 易错点 |
|---|---|---|
| 1 | 列条件桩与动作桩 | 漏掉隐含条件 |
| 2 | 确定规则数2^n | 条件个数数错 |
| 3 | 填条件项 | 真假取值漏行 |
| 4 | 填动作项并简化 | 简化时丢掉有效规则 |
因果图是在判定表之前用图形表达输入与输出的逻辑关系,涉及原因(输入)、结果(输出)以及几类约束:E(排斥,两者不能同时为真)、I(包含,至少一个为真)、O(唯一,有且仅有一个为真)、R(要求,一个为真则另一个必须为真)、M(强制,一个为真则另一个必须为假)。做题时先把原因结果列清楚,画图,再转判定表,最后生成用例。因果图题的难点在转向判定表这一步,规则数一多容易数乱,我建议用编号一行一行对,别跳步。
3.5 测试级别与策略类简答题的答题模板
这类题看着简单,但想拿满分得讲结构。比如"简述单元测试和集成测试的区别",别只写一句"单元测试测模块,集成测试测模块之间",要分维度答:测试对象不同(单元测试针对单个模块或函数,集成测试针对模块间的接口与协作)、测试依据不同(单元测试依据详细设计,集成测试依据概要设计)、发现问题类型不同(单元测试找模块内部逻辑错误,集成测试找接口和数据传递问题)、执行时机不同(单元测试在编码后,集成测试在单元测试通过后)。再比如"Alpha测试和Beta测试的区别",抓住测试地点(开发方场所 vs 用户场所)、测试人员(内部人员 vs 真实用户)、目的(发现明显问题 vs 收集真实反馈)三个点来答,层次清晰就是分。
4. 参考答案的正确打开方式
4.1 什么时候看答案才有效
参考答案是双刃剑,用好了事半功倍,用不好就是自我欺骗。我的建议是:任何一道题,先自己完整做一遍,尤其是白盒覆盖和环路复杂度这种计算题,必须独立推完、写出用例再来对答案。对的时候不要只看结果对不对,重点看用例设计的思路和答案差在哪——比如是不是漏了某个边界值,是不是把条件拆解的粒度搞错了。我见过太多同学直接翻答案"看懂了",觉得自己会了,结果临场一算全错。看懂和自己会之间隔着一道执行的墙,只有亲手推过去才算真掌握。
4.2 错题归档的三个维度
错题不要只抄一遍答案就完事,按三个维度归档才能真正提升。概念维度:因为概念不清导致的错,比如把验证和确认搞反了、把Fault和Defect混了,这类要回去补概念。方法维度:方法选错了,比如该用判定表的题用了边界值,这类要总结"什么题该用什么方法"的判断规则。计算维度:算错了、漏算了,比如环路复杂度E数漏了一条边,这类要专门练计算,把三种算法各练几遍互相验证。每道错题标上属于哪个维度,复习时按维度集中突破,效率比单纯刷题高得多。
4.3 计算题的验算技巧
计算题最大的价值在于"算完能自己验,不用等答案"。环路复杂度可以用三种方法算两遍对拍,第一种和第二种结果不一致就说明有地方数错了。白盒覆盖题可以拿设计好的用例代回程序,一步步手动执行,看是否真的覆盖到了目标语句、判定或路径——这一步很多人省了,结果写的用例根本覆盖不全自己还不知道。等价类题则检查每个等价类是否至少被一个用例覆盖,有没有哪个无效等价类被漏掉。这些验算技巧练熟了,考场上你甚至能在交卷前自己纠出一两个错。
5. 高频踩坑与排查速查
5.1 覆盖率概念混淆引发的连锁错误
最高频的坑就是覆盖率概念搞混,一个错带出一串错。典型表现:把判定覆盖当成条件覆盖,写的用例只让判定取到真假,没让每个条件都取到真假;或者把条件组合覆盖理解成"每个条件取真取假",忽略了"组合"。排查方法很简单:判定覆盖看的是整个判定表达式的结果,条件覆盖看的是每个原子条件的取值,条件组合看的是同一判定内条件取值的各种搭配。做题时先在草稿纸上把每个判定的原子条件列出来,标好编号,再逐条设计,就不容易乱。
5.2 等价类划分的争议边界
等价类题的争议多半出在划分标准上。同一个输入集合,按不同标准能划出不同的等价类,比如三角形题里"等腰"和"等边"到底算不算两个独立有效类,有的答案算,有的不算。应对办法是:先看题目的输出分类,题目要求判几种结果,你就往几个有效等价类上靠,让有效等价类和可能的输出一一对应。还有就是无效等价类一个数据只能覆盖一个无效类,不能一个非法输入同时测两个无效条件,这是铁律,违反了算错。
5.3 环路复杂度常见的三种算错
第一种,边数E数错,尤其是复合条件画成多个节点时边数变了没重数;第二种,判定节点P数错,把顺序结构里的普通节点也当成了判定节点;第三种,把复合条件拆成多个判定后再算,导致P偏大。规避方式是用两种方法各算一遍交叉验证:先用P+1快速估,再用E-N+2精算,两个结果必须一致,不一致就重画控制流图。这个习惯看着笨,但考场上能救回好几分。
5.4 简答题写得像不像"参考答案"
简答题丢分往往不是不会,而是写得太随意、没有结构。我的经验是按"分点+关键词+一句解释"来写,每一点都用关键词打头,后面跟一句说明为什么。比如答测试原则,不要流水账,列成:测试是为了发现错误;测试用例应包含合理和不合理的输入;程序员应避免测试自己的程序;测试用例应长期保留;严格执行测试计划。每点再补半句解释,卷面清晰,得分点全踩中。参考答案的评分是按关键词给分的,你写再多废话,关键词没出现也白搭。
6. 从习题到上手:把这一章真正学扎实的路径
6.1 手推一遍胜过看十遍答案
软件测试这一章最忌讳的就是"看答案学"。白盒覆盖、环路复杂度、判定表这些内容,看别人做一遍觉得简单,自己上手就出错。我的建议是找一张白纸,把教材上的经典例题盖住答案,自己从头推一遍:拆条件、列判定、设计用例、代回验证、算复杂度。推完再对答案,把差异点记下来。同一个套路推三到五道题,手就形成肌肉记忆了,考试时看到类似的程序段,思路自动就出来了。这个投入产出比非常高,比重看一遍书强太多。
6.2 用工具验证你的测试用例
如果你想让课程习题和真实工程接上,可以拿一门语言把题目里的程序真正跑起来。用Python的unittest或pytest写测试用例,把白盒覆盖题里的用例一条条实现,运行看结果是否符合预期。进阶一点,可以用覆盖率工具(比如Python的coverage、Java的JaCoCo、C的gcov)看看自己写的用例到底覆盖了多少语句和分支,再和手工推的结果对照。你会发现手工推的覆盖率和工具统计的覆盖率往往有出入,这个差异恰恰是理解覆盖本质的最好切口。工具不会骗你,它统计的语句覆盖、分支覆盖是真实执行的痕迹。
6.3 从课程习题到真实项目
课程习题里的程序段都是精心简化过的,真实项目复杂得多,但底层的测试思想完全通用。等价类划分和边界值在真实接口测试里天天用,判定表适合梳理复杂的业务规则,场景法适合设计端到端流程。你可以拿一个小项目练手,比如自己写一个计算器或待办清单程序,先写测试用例再做功能,体会一下测试驱动开发的思路。把教材里学的方法用在真实代码上,你会对"为什么要有这些方法"有完全不一样的理解——那时候再回头看习题,会觉得当年的自己算用例时漏掉边界值是理所当然的,因为没在真实场景里吃过亏。
最后说个我自己的体会:软件测试这一章,是所有软件工程章节里"投入就能立刻看到回报"的一章。概念背了就有分,方法练了就能用,用它去面试还能直接变现。我见过太多同学在这一章因为嫌算用例麻烦而放弃,结果大题白白丢十几分,特别可惜。真正动手把白盒覆盖和黑盒用例这两块推熟,剩下的都是顺手的事。你要是正在啃这一章,别急着翻答案,先拿张纸自己算——算错的地方,才是你真正需要学的地方。