软件测试基础

假设有一个三角形分类函数 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 设计的测试用例后就一定测试充分了吗?