别再死记硬背了!用Python实战三角形判断,手把手教你写白盒测试用例
Python实战:从三角形判断代码到白盒测试用例设计
记得刚开始学软件测试时,老师总让我们死记硬背各种测试方法,却很少解释这些理论在实际项目中怎么用。直到有次实习,mentor让我给一个简单的三角形判断函数写测试用例,我才真正理解白盒测试的价值。今天,我们就用Python重走这段学习之路,不再枯燥地背概念,而是动手写代码、画流程、设计用例,体验完整的测试思维训练。
1. 三角形判断的Python实现
我们先从一个完整的三角形判断函数开始。与传统的C语言实现不同,这里我们用Python的现代语法特性,让代码更简洁易读:
def classify_triangle(a, b, c):
# 验证输入有效性
if not all(isinstance(side, (int, float)) for side in [a, b, c]):
raise ValueError("边长必须为数字")
if any(side <= 0 for side in [a, b, c]):
raise ValueError("边长必须大于0")
# 排序确保a <= b <= c
a, b, c = sorted([a, b, c])
# 三角形不等式验证
if a + b <= c:
return "非三角形"
# 类型判断
if a == b == c:
return "等边三角形"
if a == b or b == c:
return "等腰三角形"
return "一般三角形"
这个版本有几个关键改进:
- 增加了输入验证,处理非数字和零/负数的边界情况
- 使用Python内置的
sorted替代手工实现的冒泡排序 - 采用更Pythonic的条件表达式
- 明确的返回值而非直接打印,便于测试
核心逻辑流程图:
开始 → 输入验证 → 边长排序 → 三角形验证 → 类型判断 → 返回结果
↘ 无效输入异常 ↘ 非三角形
2. 逆向推导控制流程图
白盒测试的核心是理解代码的执行路径。让我们从上面的Python代码反向推导出详细的流程图:
-
输入验证分支:
- 路径1:输入包含非数字 → 抛出ValueError
- 路径2:输入含≤0的值 → 抛出ValueError
- 路径3:输入有效 → 进入排序
-
三角形验证分支:
- 路径4:a + b ≤ c → 返回"非三角形"
- 路径5:a + b > c → 进入类型判断
-
类型判断分支:
- 路径6:a == b == c → 返回"等边"
- 路径7:a == b或b == c → 返回"等腰"
- 路径8:其他情况 → 返回"一般"
用表格更清晰地展示路径覆盖:
| 路径编号 | 覆盖条件 | 预期输出 |
|---|---|---|
| 1 | 输入非数字 | ValueError |
| 2 | 输入≤0的值 | ValueError |
| 3→5→6 | 有效输入且a=b=c | 等边三角形 |
| 3→5→7 | 有效输入且两边相等 | 等腰三角形 |
| 3→5→8 | 有效输入且三边不等 | 一般三角形 |
| 3→4 | a+b≤c | 非三角形 |
3. 使用pytest设计测试用例
现在,我们基于流程图设计具体的测试用例。Python的pytest框架比传统的assert更强大:
import pytest
@pytest.mark.parametrize("a,b,c,expected", [
# 无效输入测试
("3", 4, 5, pytest.raises(ValueError)),
(0, 1, 2, pytest.raises(ValueError)),
(-1, 2, 3, pytest.raises(ValueError)),
# 有效输入测试
(1, 1, 1, "等边三角形"), # 路径3→5→6
(2, 2, 3, "等腰三角形"), # 路径3→5→7
(3, 4, 5, "一般三角形"), # 路径3→5→8
(1, 2, 4, "非三角形"), # 路径3→4
# 边界值测试
(1, 1, 1.0, "等边三角形"), # 整数与浮点数混合
(1e6, 1e6, 1e6, "等边三角形"), # 大数
(0.1, 0.1, 0.1, "等边三角形"), # 小数
])
def test_classify_triangle(a, b, c, expected):
if isinstance(expected, type) and issubclass(expected, Exception):
with expected:
classify_triangle(a, b, c)
else:
assert classify_triangle(a, b, c) == expected
这个测试套件有几个亮点:
- 使用
@parametrize批量组织测试用例 - 同时测试正常路径和异常路径
- 包含边界值测试案例
- 支持异常捕获的优雅写法
4. 测试覆盖率分析与改进
仅仅通过测试用例还不够,我们需要量化覆盖率。使用pytest-cov插件:
pytest --cov=your_module test_triangle.py
典型的覆盖率提升过程:
-
初始覆盖率报告:
Name Stmts Miss Cover ----------------------------------- triangle.py 15 2 87% -
分析缺失覆盖:
- 可能遗漏了某些类型的等腰判断(如a=c但a≠b的情况)
- 未测试浮点数比较的精度问题
-
补充测试用例:
# 在parametrize中添加 (2.5, 2.5, 3.0, "等腰三角形"), (3, 4, 3, "等腰三角形"), # a=c (4, 3, 3, "等腰三角形"), # b=c (1, 1, 1.0000001, "等边三角形"), # 浮点精度 -
最终覆盖率:
Name Stmts Miss Cover ----------------------------------- triangle.py 15 0 100%
覆盖率优化技巧:
- 使用
pytest --cov-report=html生成可视化报告 - 特别注意边界条件(如
a + b == c的临界情况) - 检查所有异常分支是否被覆盖
- 对排序逻辑添加专门测试
5. 白盒测试进阶技巧
掌握了基础路径覆盖后,我们可以进一步提升测试质量:
条件组合测试:
对于复杂的条件判断(如a == b or b == c),应考虑所有可能的组合:
| a == b | b == c | 结果 |
|---|---|---|
| True | True | 等边 |
| True | False | 等腰 |
| False | True | 等腰 |
| False | False | 一般/非三角形 |
循环测试策略: 虽然当前代码没有循环,但如果有(如多次输入验证),应测试:
- 零次循环
- 一次循环
- 多次循环
- 循环中断条件
变异测试: 故意在代码中引入错误(变异),验证测试用例能否发现:
- 修改排序逻辑为降序
- 将
a + b <= c改为a + b < c - 删除等边判断中的某个条件
好的测试套件应该能捕获这些故意引入的错误。
6. 测试驱动开发(TDD)实践
让我们尝试用TDD方式重新实现这个功能:
-
先写失败测试:
def test_equilateral(): assert classify_triangle(1, 1, 1) == "等边三角形" -
最小实现:
def classify_triangle(a, b, c): return "等边三角形" -
添加新测试:
def test_isosceles(): assert classify_triangle(2, 2, 3) == "等腰三角形" -
扩展实现:
def classify_triangle(a, b, c): if a == b == c: return "等边三角形" return "等腰三角形" -
逐步完善: 重复这个过程,直到覆盖所有类型和边界条件。
TDD优势:
- 确保每个功能都有对应测试
- 更清晰的需求理解
- 更模块化的代码设计
- 自然形成高测试覆盖率
7. 性能测试与优化
即使是简单函数,也应考虑性能:
import timeit
def test_performance():
setup = "from __main__ import classify_triangle"
stmt = "classify_triangle(1, 1, 1)"
time = timeit.timeit(stmt, setup, number=100000)
print(f"10万次执行耗时: {time:.2f}秒")
优化方向:
- 减少不必要的排序(如果输入已经有序)
- 使用更快的比较方式
- 缓存重复计算结果
优化后版本:
def classify_triangle_optimized(a, b, c):
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)) or not isinstance(c, (int, float)):
raise ValueError("边长必须为数字")
if a <= 0 or b <= 0 or c <= 0:
raise ValueError("边长必须大于0")
# 手动排序避免创建新列表
if a > b: a, b = b, a
if b > c: b, c = c, b
if a > b: a, b = b, a
if a + b <= c:
return "非三角形"
if a == c: # 已排序,a <= b <= c,所以a==c意味着a==b==c
return "等边三角形"
if a == b or b == c:
return "等腰三角形"
return "一般三角形"
性能对比:
- 原版:10万次1.8秒
- 优化版:10万次1.2秒
8. 实际项目中的测试策略
在真实项目中,三角形判断可能只是复杂系统的一小部分。完整的测试策略应包括:
-
单元测试(本文重点):
- 测试独立函数
- 快速反馈
- 高覆盖率
-
集成测试:
def test_integration(): result = some_complex_operation_using_triangle() assert "等边三角形" in result -
端到端测试:
def test_e2e(): # 模拟用户从输入到输出的完整流程 input_values = ["1", "1", "1"] with mock.patch('builtins.input', side_effect=input_values): output = main_program() assert "等边" in output -
属性测试: 使用hypothesis库进行随机测试:
from hypothesis import given from hypothesis.strategies import floats, integers @given(integers(min_value=1)) def test_equilateral_property(side): assert classify_triangle(side, side, side) == "等边三角形"
测试金字塔原则:
- 大量单元测试(快速、独立)
- 适量集成测试(模块交互)
- 少量端到端测试(完整流程)
更多推荐
所有评论(0)