计算器黑盒测试实战:等价类划分与边界值分析及MFC源码复盘
简介西南科技大学计算机学院的计算器黑盒测试实验报告面向软件测试初学者与高校计算机专业学生系统展示了黑盒测试中等价类划分和边界值分析两种方法在简单计算器程序上的完整应用。报告从测试目的、测试内容、测试步骤到结果分析全程记录用例覆盖整数、小数、负数和无效输入四类等价类并对加、减、乘、除运算的边界值如0、1、101、除数为0逐项验证同时指出程序在非法输入处理上存在缺陷并提出改进建议。资源为1个pdf文件压缩包大小仅535KB报告内附测试界面截图、结果对照表及附录源代码便于课程设计、实验报告撰写或自学黑盒测试时直接参考。已有168人学习这份实操性强的报告能帮助读者将测试理论快速转化为可执行的测试方案。1. 计算器黑盒测试实验报告等价类、边界值与 MFC 源码的完整复盘一台只做加减乘除的计算器是把黑盒测试讲清楚的最小样本。这份西南科技大学计算机学院的实验报告 PDF完整走了一遍黑盒测试流程用等价类划分把输入域切成整型、小数、负数、无效输入四组用边界值分析法给四则运算各设计了 14 条边界用例逐条执行并截图最后附上完整的 MFC 源码。对刚入行的测试人员这是一份可以直接套用的用例设计模板对写过几年测试的人价值反而在那些自相矛盾的地方除法边界用例把 10/0 的预期写成正常运算源码里却明明有除零保护无效输入无法输入的结论暴露的是输入通道设计而不是校验逻辑。把这几处掰开才看得见黑盒测试里 oracle 设计和用例可行性的真实分量。2. 等价类划分设计整数、小数、负数与无效输入的用例粒度2.1 黑盒测试为什么从等价类划分开始黑盒测试把程序当成不透明的盒子只依据需求规格设计输入并核对输出不关心内部路径白盒测试则要覆盖分支、条件和路径。两者不是二选一而是同一程序在不同阶段的观察视角。计算器这种程序输入域看似是连续的 double理论上用例无限多但同类型的输入走的是同一段处理逻辑所以按数据类型划分等价类是成本最低的降维方式。比如整型 5550 和小数 25.312.7在 MFC 的按键处理里走的是完全相同的内存运算路径差别只在小数输入时多了 Ispoint 分支。因此每个等价类取一个代表值就能代表整类行为。这里有一个常见的理解偏差等价类划分的价值在于类与类之间的行为差异而不是类内的数值差异。如果把 5550、5651、5752 连续设计三条用例属于重复覆盖对发现缺陷没有增量。报告里每个运算符只取 4 个等价类的做法粒度是及格的。2.2 计算器的四个等价类与代表用例报告按加法、减法、乘法、除法四个运算符分别取样每个运算符配 4 个等价类共 16 条用例等价类加减乘除预期输出整型555078-2415*2536/4正常运算小数25.312.714.3-11.725.6*12.850.2/20.7正常运算负数-20(-21)(-15)-(-14)-12*-12-16/-5正常运算无效输入E1t2G4-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 条用例的预期大多为正常运算唯独 Test8100的预期是不能运算减法、乘法表里同样出现了 10?0 预期不能运算Test操作数 a操作数 b加减乘预期除法预期1010正常运算正常运算2110正常运算正常运算34010正常运算正常运算455.510正常运算正常运算5-7810正常运算正常运算610010正常运算正常运算710110正常运算正常运算8100报告预期不能运算报告预期正常运算9-14101/40/55.5/-78/100/101正常运算正常运算这里存在明显的 oracle 错误100 在数学和需求上都合法应当正常显示 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}, // 100 必须正常 {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*10digit 累进小数部分用 digit/pow(10,Sumpoint) 拼接。这样的实现有一个容易被黑盒用例抓住的特性如果 m_parameter 里已经停着一个结果值再按数字键会把结果当作新操作数的前缀直接拼接而不是清空重来。也就是说5550 得到 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); }这段代码有两个可以在黑盒测试中反推出来的行为。第一函数末尾把所有状态全部清零说明这是一台单步执行的计算器每次按 后结果只存在于显示区无法作为下一次链式运算的操作数。如果想在 5550 的结果上继续 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 的状态机。本文还有配套的精品资源点击获取