3、自动化测试框架:Python测试框架选型(pytest/unittest)、测试用例设计原则、测试固件(fixture)管理、测试报告生成

好,咱们进入第三章。这一章我打算聊聊自动化测试框架的选型与实战。说实话,在基站产线上摸爬滚打这么多年,我见过太多因为测试框架选型不当导致的“惨案”。有的团队用unittest硬扛,结果用例一多维护成本爆炸;有的盲目追求pytest的炫酷功能,结果把简单问题搞复杂了。今天我就把这两者的优劣、设计原则、固件管理以及报告生成,掰开了揉碎了讲给你听。

3.1 Python测试框架选型:pytest vs unittest

先说说这两个框架。unittest是Python标准库自带的,开箱即用,不需要额外安装。pytest则是第三方库,功能更强大,语法更简洁。我个人习惯是:新项目首选pytest,老项目如果已经用了unittest,也不急着迁移

为什么会这样?你想想看,unittest的设计思路是模仿Java的JUnit,所以它要求你写测试类、继承TestCase、用assertEqual这种冗长的断言。而pytest呢?直接写函数,用Python原生的assert就行。在产线测试这种需要快速迭代的场景下,pytest的简洁性优势太明显了。

对比维度 pytest unittest
安装方式 pip install pytest 标准库,无需安装
用例编写 函数式,assert断言 类式,assertEqual等
固件管理 fixture装饰器,灵活 setUp/tearDown,固定
参数化 内置@pytest.mark.parametrize 需借助ddt库或subTest
插件生态 丰富(报告、并行、重试等) 较少
产线适用性 高(灵活、可扩展) 中(适合简单场景)

我在项目中遇到过这样的情况:一个产线校准脚本,需要测试20个频点、3种功率等级、2种调制方式。如果用unittest,你得写120个测试方法,或者用subTest嵌套循环,代码可读性很差。换成pytest的parametrize,几行代码就搞定了。嗯,这里要注意:产线测试的用例数量通常很大,参数化能力是刚需

核心建议:如果你是从零开始搭建基站自动化测试框架,直接选pytest。如果你在维护一个已经用unittest写好的老框架,也别急着重构——先把功能跑通,后续再逐步迁移。

3.2 测试用例设计原则

框架选好了,接下来就是怎么写测试用例。很多新手容易犯一个错误:把测试用例写成“脚本”,一个函数里塞了几百行代码,测了十几个功能点。这种用例一旦失败,你根本不知道是哪个环节出了问题。

我总结了几条原则,你可以参考一下:

  • 单一职责原则:一个测试用例只验证一个功能点。比如“测试PA在最大功率下的线性度”和“测试PA在最小功率下的效率”,这是两个用例,别混在一起。
  • 可重复性原则:测试用例应该能反复执行,结果一致。产线环境经常有干扰,所以用例里要加入重试机制和超时控制。
  • 独立性原则:用例之间不能有依赖。A用例失败了,B用例应该照样能跑。我曾经见过一个项目,测试用例按顺序执行没问题,一并行就全挂——就是因为用例之间共享了全局变量。
  • 可读性原则:用例名称要能说明测试内容。别用test_1、test_2这种名字。我习惯用“test_被测对象_测试条件_预期结果”的命名方式,比如test_pa_max_power_linearity_pass。

避坑指南:我曾经在产线上遇到一个诡异的问题——某个测试用例偶尔会失败,但重跑一次就过了。排查了半天,发现是用例里没有释放仪器资源,导致下一个用例拿不到GPIB句柄。从那以后,我要求所有用例都必须显式释放资源,哪怕用try-finally也要保证。

3.3 测试固件(fixture)管理

固件管理,说白了就是测试前后的准备和清理工作。在基站测试中,常见的固件包括:仪器连接、仪表配置、DUT上电、加载校准文件等。

pytest的fixture机制非常灵活。你可以定义不同作用域的fixture:

  • session级别:整个测试会话只执行一次,适合初始化仪器驱动、加载全局配置。
  • module级别:每个模块执行一次,适合加载模块级别的测试数据。
  • class级别:每个测试类执行一次,适合类级别的资源分配。
  • function级别:每个测试函数执行一次,适合最细粒度的资源管理。

举个例子,在产线校准中,我们通常需要连接频谱仪和信号源。如果每个用例都重新连接一次,那效率太低了。我会这样设计:

import pytest

@pytest.fixture(scope="session")
def spectrum_analyzer():
    """初始化频谱仪,整个测试会话只连接一次"""
    sa = SpectrumAnalyzer("GPIB0::10::INSTR")
    sa.reset()
    sa.configure(freq=2.4e9, span=100e6)
    yield sa
    sa.close()  # 测试结束后清理

@pytest.fixture(scope="function")
def dut_power_on():
    """每个用例前给DUT上电,用例结束后断电"""
    power_supply.on()
    yield
    power_supply.off()

def test_pa_gain(spectrum_analyzer, dut_power_on):
    """测试PA增益"""
    result = spectrum_analyzer.measure_gain()
    assert result > 20.0  # 增益大于20dB

你看,这样设计的好处是:频谱仪只连接一次,节省了大量时间;而DUT上电在每个用例前后都执行,保证了测试环境的独立性。嗯,这里要注意:fixture的作用域选择要权衡效率和隔离性。作用域越大,效率越高,但隔离性越差;作用域越小,隔离性越好,但效率越低。

警告:千万别在session级别的fixture里修改全局状态!我见过有人把仪器配置写在session fixture里,结果一个用例改了配置,后面的用例全挂了。如果确实需要修改,请使用function级别的fixture,或者在用例内部临时修改并恢复。

3.4 测试报告生成

测试跑完了,报告怎么出?在产线上,报告可不是给程序员看的,而是给产线工人、质量工程师、甚至客户看的。所以报告要清晰、直观、包含关键信息。

pytest生态里有两个常用的报告插件:

  • pytest-html:生成漂亮的HTML报告,包含用例通过/失败统计、执行时间、错误信息等。
  • allure-pytest:生成更专业的Allure报告,支持步骤分解、截图嵌入、历史趋势等。

我个人习惯在产线场景下使用pytest-html,因为它轻量、生成快、不需要额外部署服务。用法很简单:

# 命令行运行
pytest test_calibration.py --html=report.html --self-contained-html

# 或者在pytest.ini中配置
[pytest]
addopts = --html=report.html --self-contained-html

如果你需要更详细的报告,比如每个测试步骤的耗时、仪器状态截图、校准曲线等,我建议用allure。它支持在用例中嵌入附件:

import allure

@allure.step("测量PA增益")
def measure_gain():
    # 执行测量
    pass

@allure.attach.file("screenshot.png", name="频谱截图", attachment_type=allure.attachment_type.PNG)
def test_pa_gain():
    measure_gain()
    assert True

产线报告还有一个关键点:失败用例的快速定位。我建议在报告中加入以下信息:

  • 测试时间戳
  • DUT序列号
  • 仪器配置快照
  • 失败时的关键参数值
  • 错误堆栈(给开发看)和简化错误描述(给产线工人看)

小技巧:在产线上,我习惯把报告文件名加上时间戳和批次号,比如“calibration_report_20250115_batch003.html”。这样方便追溯,也方便质量部门做统计分析。另外,报告里最好加一个“通过率”的汇总条,一眼就能看出整批产品的质量状况。

好了,这一章的内容就到这里。框架选型、用例设计、固件管理、报告生成,这四个点构成了自动化测试框架的核心。下一章我会聊聊测试数据管理,包括如何设计测试用例的输入输出、如何管理校准参数、以及如何与数据库对接。到时候见。