✍ 作者:@LVZI | 标签:白盒测试、单元测试、Spring Boot、文件上传、找 bug、面试实战
白盒测试(White Box Testing)也被称为结构测试、透明盒测试、逻辑驱动测试,是软件测试中的一种重要类型。
与黑盒测试关注“功能是否正确”不同,白盒测试关注程序的内部逻辑结构是否正确。
白盒测试 = “看得见代码的测试”,设计者需要了解程序内部实现。
方法 | 简述 |
---|---|
语句覆盖(Statement Coverage) | 每一行代码都执行一次 |
分支覆盖(Branch Coverage) | 每一个 if/else、switch 的每个分支都至少走一次 |
条件覆盖(Condition Coverage) | 每个条件表达式的真假情况都覆盖一次 |
路径覆盖(Path Coverage) | 所有可能的执行路径都走一次(更完整但更复杂) |
在我的项目中,有一个上传音乐的功能。核心目标是:确保用户上传的是一个合法的 MP3 文件。
起初这个逻辑比较粗糙,只是通过文件后缀名判断是否为 .mp3
,但后来我通过白盒测试发现了不少漏洞,逐步对其进行了三阶段优化:
阶段 | 方法 | 严谨程度 | 是否应用白盒测试 |
---|---|---|---|
① | 判断文件后缀是否为 .mp3 |
❌ 最低,仅形式校验 | ✅ 是 |
② | 判断文件前3字节是否为 ID3 (MP3 文件头) |
✅ 中级,可判断格式合法性 | ✅ 是 |
③ | 使用音频库读取 MP3 元数据 | ✅✅✅ 最严谨,可读取标签信息 | 否,仅调研 |
String filename = file.getOriginalFilename();
if (!filename.toLowerCase().endsWith(".mp3")) {
return new ResponseBodyMessage<>(-1, "文件不是MP3", false);
}
我们根据“条件分支 + 边界值分析”设计了如下测试用例:
用例编号 | 输入文件名 | 预期结果 | 涵盖分支 |
---|---|---|---|
TC1 | song.mp3 |
✅ 允许上传 | 正常流程 |
TC2 | SONG.MP3 |
✅ 允许上传 | 忽略大小写 |
TC3 | song.txt |
❌ 拒绝 | 错误格式 |
TC4 | song.mp3.exe |
❌ 拒绝 | 嵌套后缀 |
TC5 | .mp3 (空歌名) |
❌ 拒绝 | 边界值 |
TC6 | null 文件名 |
⚠ 抛异常 | 未处理空指针路径 |
song.mp3.exe
也能通过校验;file.getOriginalFilename() == null
);private boolean isValidMp3(MultipartFile file) {
try (InputStream inputStream = file.getInputStream()) {
byte[] header = new byte[3];
if (inputStream.read(header) != 3) return false;
return header[0] == 'I' && header[1] == 'D' && header[2] == '3';
} catch (IOException e) {
return false;
}
}
用例编号 | 模拟文件头 | 预期结果 | 目标路径 |
---|---|---|---|
TC1 | ID3 |
✅ 通过 | 正常分支 |
TC2 | ABC |
❌ 拒绝 | 条件分支 |
TC3 | I , D (仅2字节) |
❌ 拒绝 | 不足3字节 |
TC4 | 抛出 IOException | ❌ 拒绝 | 异常处理路径 |
==
比较触发情况 ✔为了进一步提升准确性,可以使用专业音频处理库,如:
工具名 | 用途 | 是否推荐 |
---|---|---|
mp3agic | Java 中读取 ID3 标签信息 | ✅ |
jaudiotagger | 提取歌手/专辑信息等元数据 | ✅ |
FFmpeg | 通用媒体格式分析工具 | ✅(需搭配命令行) |
不过此方案在项目中暂未采用,仅供扩展思考。
当面试官问:“你有没有在项目中做白盒测试?怎么做的?”
你可以这样说:
回答示例:
在我做的校园音乐分享平台中,我负责音乐上传功能。这个功能需要判断用户上传的文件是否是 MP3 格式。
初期我是通过文件名后缀判断的,但通过白盒测试设计了多个路径和边界值用例,如
.mp3.exe
、空文件名、大小写等,发现很多漏洞;后来我重构了逻辑,改为读取文件头判断是否为
ID3
标签,并通过白盒测试覆盖了所有正常分支、非法格式和异常路径,确保了逻辑正确性;整个过程中我使用 JUnit 编写了对应的单元测试用例,并在代码结构上做了可测性优化,比如将校验方法封装为
isValidMp3()
;这个过程不仅让我发现了 bug,也让我从代码结构、测试覆盖率和异常分支处理上都提升了一个层级。