计算器黑盒测试实战:等价类划分与边界值分析及MFC源码复盘
2026/9/19 16:32:42 网站建设 项目流程

简介:西南科技大学计算机学院的计算器黑盒测试实验报告,面向软件测试初学者与高校计算机专业学生,系统展示了黑盒测试中等价类划分和边界值分析两种方法在简单计算器程序上的完整应用。报告从测试目的、测试内容、测试步骤到结果分析全程记录,用例覆盖整数、小数、负数和无效输入四类等价类,并对加、减、乘、除运算的边界值(如0、1、101、除数为0)逐项验证,同时指出程序在非法输入处理上存在缺陷并提出改进建议。资源为1个pdf文件,压缩包大小仅535KB,报告内附测试界面截图、结果对照表及附录源代码,便于课程设计、实验报告撰写或自学黑盒测试时直接参考。已有168人学习,这份实操性强的报告能帮助读者将测试理论快速转化为可执行的测试方案。

1. 计算器黑盒测试实验报告:等价类、边界值与 MFC 源码的完整复盘

一台只做加减乘除的计算器,是把黑盒测试讲清楚的最小样本。这份西南科技大学计算机学院的实验报告 PDF,完整走了一遍黑盒测试流程:用等价类划分把输入域切成整型、小数、负数、无效输入四组,用边界值分析法给四则运算各设计了 14 条边界用例,逐条执行并截图,最后附上完整的 MFC 源码。对刚入行的测试人员,这是一份可以直接套用的用例设计模板;对写过几年测试的人,价值反而在那些自相矛盾的地方:除法边界用例把 10/0 的预期写成"正常运算",源码里却明明有除零保护;无效输入"无法输入"的结论,暴露的是输入通道设计而不是校验逻辑。把这几处掰开,才看得见黑盒测试里 oracle 设计和用例可行性的真实分量。

2. 等价类划分设计:整数、小数、负数与无效输入的用例粒度

2.1 黑盒测试为什么从等价类划分开始

黑盒测试把程序当成不透明的盒子,只依据需求规格设计输入并核对输出,不关心内部路径;白盒测试则要覆盖分支、条件和路径。两者不是二选一,而是同一程序在不同阶段的观察视角。计算器这种程序,输入域看似是连续的 double,理论上用例无限多,但同类型的输入走的是同一段处理逻辑,所以按数据类型划分等价类是成本最低的降维方式。比如整型 55+50 和小数 25.3+12.7,在 MFC 的按键处理里走的是完全相同的内存运算路径,差别只在小数输入时多了 Ispoint 分支。因此每个等价类取一个代表值,就能代表整类行为。

这里有一个常见的理解偏差:等价类划分的价值在于"类与类之间的行为差异",而不是"类内的数值差异"。如果把 55+50、56+51、57+52 连续设计三条用例,属于重复覆盖,对发现缺陷没有增量。报告里每个运算符只取 4 个等价类的做法,粒度是及格的。

2.2 计算器的四个等价类与代表用例

报告按加法、减法、乘法、除法四个运算符分别取样,每个运算符配 4 个等价类,共 16 条用例:

等价类预期输出
整型55+5078-2415*2536/4正常运算
小数25.3+12.714.3-11.725.6*12.850.2/20.7正常运算
负数-20+(-21)(-15)-(-14)-12*-12-16/-5正常运算
无效输入E1+t2G4-k5I5*l6Ff/se非法操作,无法输入

这个划分的粒度值得肯定:加法和乘法对整型、小数的处理路径几乎一致,但除法引入除零边界,负数运算涉及负号输入和括号写法,单独列类能覆盖到不同的按键序列。无效输入类的设计意图是验证程序对非数字字符的响应——如果计算器接受键盘输入,这类用例能测出字符过滤和类型转换的缺陷;如果只接受按钮输入,它测的就是另一回事了。

2.3 无效输入测到的不是校验,而是输入通道

4 条无效用例的执行结果都是"程序中无效数字无法正常输入,程序无法进行"。这句话要拆开看:被测程序的编辑框 IDC_EDIT 只用于显示 m_parameter,数字全部通过 Onpara0 到 Onpara9 按钮输入,非法字符在 UI 上根本没有入口。也就是说,"无法输入"是按钮式输入通道的天然结果,而不是程序对非法字符做了校验。要想真正测试输入校验逻辑,得用 WM_SETTEXT 向编辑框注入文本,或者运行时把编辑框改成可编辑状态再输入。

// 数字键 1 的处理逻辑:编辑框只做显示,不接收键盘输入 void CCalculateDlg::Onpara1() { UpdateData(true); // 控件值读入 m_parameter if (!Ispoint) { CalculatePara = m_parameter * 10 + 1; // 整数部分:前值左移一位再加 1 } else { CalculatePara = m_parameter + 1 / pow(10, Sumpoint); // 小数:按位权追加 Sumpoint++; } m_parameter = CalculatePara; UpdateData(false); // 写回编辑框显示 }

这里 UpdateData(true) 把控件内容同步到成员变量,UpdateData(false) 反向写回;Ispoint 标记是否进入小数点输入状态,Sumpoint 记录当前是第几位小数,pow(10, Sumpoint) 决定 1 应该落在十分位还是百分位。整段代码里没有任何字符过滤,因为设计上就不存在自由文本输入。

对黑盒测试来说,这条结论的启示是:用例必须贴着真实输入通道设计,否则测出来的只是 UI 约束。此外,负数用例执行时报告注明"算式写法错误导致正常运算错误",说明 -20+(-21) 这种带括号的写法在单运算符计算器上根本按不出来,属于执行方法问题。写测试记录时,应该把实际按键序列原样写下来,否则后人无法复现,更无从判断是程序缺陷还是操作失误。

3. 边界值分析法在四则运算上的取点与实测

3.1 边界值取点的依据:错误集中在域的边缘

经验规律是,缺陷更容易出现在输入域的边界附近,例如 0、正负切换、进位位置。报告的做法是固定一个操作数为 10,让另一个操作数依次取 0、1、40、55.5、-78、100、101,再将两个操作数位置对调,每个运算符得到 14 条用例。取点意图很清楚:0 是加减法的零元,也是除法最危险的除数;1 是乘除法的单位元;100 和 101 覆盖从两位到三位的位数过渡;55.5 是二进制可精确表示的半整数,作为小数代表值很合适——真正考验精度的是 25.3、50.2 这类无法用二进制有限位表示的数值。固定一个操作数、只变化另一个,是单变量原则的简化应用,在成本受限的实验场景下是务实选择。

3.2 加减乘的边界用例与 oracle 问题

以加法为例,14 条用例的预期大多为"正常运算",唯独 Test8(10+0)的预期是"不能运算",减法、乘法表里同样出现了 10?0 预期"不能运算":

Test操作数 a操作数 b加减乘预期除法预期
1010正常运算正常运算
2110正常运算正常运算
34010正常运算正常运算
455.510正常运算正常运算
5-7810正常运算正常运算
610010正常运算正常运算
710110正常运算正常运算
8100报告预期"不能运算"报告预期"正常运算"
9-14101/40/55.5/-78/100/101正常运算正常运算

这里存在明显的 oracle 错误:10+0 在数学和需求上都合法,应当正常显示 10;而 10/0 是除零,应当报错。测试人员不能凭直觉把 0 当作非法输入,预期输出必须回到需求规格核对。报告在 Test8 预期错误的前提下,得出"测试结果运算均属正常"的结论,等于用一个错误的判定标准得出了全绿的结论。下面把边界值用例参数化,方便逐条核对:

// 边界值用例参数化:每个运算符 14 条,这里是加法示例 struct BndCase { double a, b; char op; int expectOk; }; BndCase bnd[] = { {0, 10, '+', 1}, {1, 10, '+', 1}, {40, 10, '+', 1}, {55.5, 10, '+', 1}, {-78, 10, '+', 1}, {100, 10, '+', 1}, {101, 10, '+', 1}, {10, 0, '+', 1}, // 10+0 必须正常 {10, 1, '+', 1}, {10, 40, '+', 1}, {10, 55.5, '+', 1}, {10, -78, '+', 1}, {10, 100, '+', 1}, {10, 101, '+', 1}, };

每条用例都带预期结果 expectOk,实际执行时逐条比对并记录 pass/fail。边界值法的价值不在于用例数量,而在于每一条都对准一个可能的实现分歧点。减法、乘法表结构完全相同,只需把 op 换成 '-' 和 '*'。

3.3 除法边界:预期与实现的正面冲突

除法表的 Test8 是 10/0,预期输出写的却是"正常运算",而源码里存在专门的除零分支(见第四章代码)。对照之下问题很清楚:测试的预期没有经过需求确认,实现却已经定义了行为——除数为 0 时归零并提示。测试者显然没有把预期输出和源码行为对齐,导致最值得记录的一条除法边界用例失去了判定意义。

同时,小数边界 50.2/20.7 这类用例在 double 下必然产生近似结果,报告截图只显示"正常",没有记录精确输出值。黑盒测试对计算器这类数值程序,预期输出应当写成"与期望值的绝对差小于 1e-9",否则浮点误差和真实缺陷无法区分。这也是边界值分析里最容易被漏掉的一层:边界不仅是数值边界,还包括数值精度边界。

4. 从 MFC 源码反推测试结论:状态机、除零与浮点误差

4.1 三个核心变量与按键输入的状态机

要理解黑盒测试结果,先得看懂被测程序的状态设计。CCalculateDlg 里有几个关键成员,它们的生命周期决定了计算器的行为边界:

变量作用何时清零
m_parameter编辑框当前显示值,也是正在输入的操作数每次按键都会更新
CalculatePara当前操作数缓存,按下 "=" 时从 m_parameter 取值Oncalculate 末尾清零
CalculateResult运算结果累加器Oncalculate 末尾清零
CalculateExpre当前运算符('+'、'-'、'*'、'/')按下运算符键时设置
Ispoint / Sumpoint小数输入状态与小数位数Oncalculate 末尾清零

数字按键的逻辑已在第二章看过:整数部分用 m_parameter*10+digit 累进,小数部分用 digit/pow(10,Sumpoint) 拼接。这样的实现有一个容易被黑盒用例抓住的特性:如果 m_parameter 里已经停着一个结果值,再按数字键会把结果当作新操作数的前缀直接拼接,而不是清空重来。也就是说,55+50 得到 105 后再按 3,显示的是 1053。这种"连按数字追加到结果"的行为如果不写进用例预期,回归时很容易被当成缺陷——其实是这套简单实现的设计如此。

4.2 Oncalculate 的执行链与连续运算缺陷

按 "=" 时进入 Oncalculate,这是整个程序最核心的分支逻辑,也是典型的加减乘除计算器简单代码实现:

void CCalculateDlg::Oncalculate() { UpdateData(true); CalculatePara = m_parameter; switch (CalculateExpre) { case '+': CalculateResult += CalculatePara; m_parameter = CalculateResult; break; case '-': CalculateResult -= CalculatePara; m_parameter = CalculateResult; break; case '*': CalculateResult *= CalculatePara; m_parameter = CalculateResult; break; case '/': if (CalculatePara) // 除数为 0 时的保护分支 { CalculateResult /= CalculatePara; m_parameter = CalculateResult; } else { m_parameter = 0; // 把显示归零并给出提示 // 除数不能为零 } break; } CalculatePara = 0; CalculateResult = 0; Ispoint = false; Sumpoint = 0; UpdateData(false); }

这段代码有两个可以在黑盒测试中反推出来的行为。第一,函数末尾把所有状态全部清零,说明这是一台单步执行的计算器:每次按 "=" 后,结果只存在于显示区,无法作为下一次链式运算的操作数。如果想在 55+50 的结果上继续 +1,按下 "+" 时必须由 Onplus 把显示值写回 CalculateResult——但报告提供的代码片段里没有贴出 Onplus 的实现,从清零逻辑看,链式运算的状态衔接完全依赖那几个没展示的函数,这是测试时最容易出现"偶发错误"的区域。第二,switch 没有 default 分支,如果用户没按任何运算符就直接按 "=",程序静默返回,界面上没有任何反馈。等价类划分里如果补一个"空运算符"用例,就能把这类行为暴露出来。

4.3 除零分支的实现细节与 double 比较

除零处理用了 if (CalculatePara) 直接判断 double 是否等于 0。在按钮输入路径上,用户确实按不出一个 1e-320 的除数,所以这个判断在 UI 层够用;但如果计算器被扩展成接受文本输入,或承接上一步运算结果,极小的非零值会把除法推到无穷大。工程上更稳妥的写法是判断绝对值小于某个 epsilon,比如 fabs(CalculatePara) < 1e-9。另外,原报告 PDF 里"除数不能为零"这几个字直接跟在 m_parameter = 0 后面,像是丢失了注释符。这种排版问题在实验报告里很难排查,也侧面说明源码是从工程里拷贝出来但没有经过格式化。

5. 把实验报告升级成可复现的回归基线

5.1 抽一个可单测的 Compute 核心

不管界面是 MFC 还是 Qt 计算器,四则运算的核心逻辑都可以从对话框里抽出来,变成一个纯函数。这样 16 条等价类用例和每个运算符 14 条边界值用例,就能脱离 UI 直接批量执行:

// 抽取核心运算:返回 false 表示除零或非法运算符 bool Compute(double lhs, char op, double rhs, double& out) { switch (op) { case '+': out = lhs + rhs; return true; case '-': out = lhs - rhs; return true; case '*': out = lhs * rhs; return true; case '/': if (fabs(rhs) < 1e-9) return false; // epsilon 判断,替代直接比较 0 out = lhs / rhs; return true; default: return false; // 未定义运算符 } }

参数说明:lhs 是第一个操作数,rhs 是第二个操作数,op 是运算符,out 是输出参数;返回值区分"正常结果"和"异常路径"。这样除零用例的预期就可以写成 false,而不是含糊的"正常运算"。

5.2 用例表落地与回归循环

把报告里的用例落成 CSV,是让这份实验报告持续产生价值的最快方式:

op,a,b,expect,note +,55,50,105,整型等价类 +,25.3,12.7,38,小数等价类 /,10,0,ERROR,边界值除零 /,50.2,20.7,2.42512077294686,小数除法

再写一个几十行的脚本逐条读取、调用 Compute、按浮点差值的绝对值小于 1e-9 判定通过。之后每次改代码,先跑一遍基线,再对着失败用例判断是预期错了还是实现回归了。具体建议是把三条用例固定为回归基线的前三条:10/0 除零、按 "=" 无运算符、结果后连按数字追加。这三条是这份报告里唯一真正触到程序行为边界的用例,其余大多数等价类和边界值用例,老实说只是"运行正常"的重复确认。把这三条放在回归脚本最前面,任何一个版本改了它们的行为,先别急着查业务逻辑,回读 Oncalculate 的状态机。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询