软件测试基础
假设有一个三角形分类函数 classify_triangle(),其功能是根据三条边的长度判断三角形类型:
- 三条边相等:等边三角形 (equilateral)
- 两条边相等:等腰三角形 (isosceles)
- 三条边均不相等:普通三角形 (scalene)
- 三条边不能组成三角形:非法三角形 (invalid)
def classify_triangle(a, b, c):
# Check whether the side lengths are valid
if a <= 0 or b <= 0 or c <= 0:
return "invalid"
# The sum of any two sides must be greater than the third side
if a + b <= c or a + c <= b or b + c <= a:
return "invalid"
# All three sides are equal
if a == b == c:
return "equilateral"
# Any two sides are equal
if a == b or a == c or b == c:
return "isosceles"
return "scalene"
为了编写测试代码对该函数进行测试,我们通常会借助自动化测试框架,例如 Python 中的 pytest。
对于判断普通三角形的行为,我们可以编写如下测试代码:
import pytest
from triangle import classify_triangle
def test_classifies_scalene_triangle():
# Arrange
sides = (3, 4, 5)
# Act
actual = classify_triangle(*sides)
# Assert
assert actual == "scalene"
然后运行:
python -m pytest -v
pytest 会自动执行当前目录下以 test_.py 开头的测试代码,并输出每个测试的执行结果。
为了保持测试代码的结构清晰,上述 test_classifies_scalene_triangle() 使用了 Arrange-Act-Assert (AAA) 方式进行组织:
- Arrange:准备测试数据和测试环境(测试条件是什么)
- Act:执行被测程序(执行了什么操作)
- Assert:检查实际结果(期望得到什么结果)
这里最后一步的 assert 语句非常重要,如果没有该语句,则程序的任何输出(只要不奔溃)都可被简单视为测试通过。
此外,即使对于简单的三角形分类函数,只执行 “一次测试” 也显然是不充分的。当需要执行多组测试时,通常会存在测试方法和测试步骤相同,而仅有测试输入和预期输出不同的情况。为此,可以使用参数化来减少重复代码(将测试逻辑和测试数据分离):
pytest.mark.parametrize(
("a", "b", "c", "expected"),
[
pytest.param(3, 3, 3, "equilateral", id="equilateral"),
pytest.param(3, 3, 2, "isosceles", id="isosceles-ab"),
pytest.param(3, 2, 3, "isosceles", id="isosceles-ac"),
pytest.param(2, 3, 3, "isosceles", id="isosceles-bc"),
pytest.param(3, 4, 5, "scalene", id="scalene"),
],
)
def test_classifies_triangle(a, b, c, expected):
assert classify_triangle(a, b, c) == expected
在执行 pytest 时,pytest 会分别用参数表中的每一组数据来运行 test_classifies_triangle(),并汇报对应的测试结果。
自动化测试框架已经为我们提供了自动执行测试代码的能力,但其无法帮我们决定应该选择什么测试输入数据、以及判断给定测试输入对应的预期输出结果。对于上述待测三角形分类函数:
- 参数化测试的测试输入足够充分吗?还有什么尚未考虑到的情况?
- LLM 能帮我们额外设计哪些测试用例?增加 LLM 设计的测试用例后就一定测试充分了吗?