智能座舱测试脚本编写实战 - 第4节

4. 自动化测试框架选型:Pytest vs Unittest

做智能座舱测试,选对框架等于成功了一半。我见过不少团队,一开始用 Unittest 写了几百个用例,后来发现维护成本越来越高,跑一次要半小时,最后不得不重构。嗯,今天我们就来聊聊这两个框架,以及我为什么最终选择了 Pytest。

4.1 Unittest:Python 自带的“老大哥”

Unittest 是 Python 标准库的一部分,开箱即用。它的设计思路参考了 Java 的 JUnit,所以如果你有 Java 背景,上手会很快。

核心特点:

  • 不需要额外安装,Python 装好就有
  • 强制使用类继承(TestCase)来组织用例
  • 自带 setUp / tearDown 方法,用于前置和后置操作
  • 断言方法丰富(assertEqual、assertTrue、assertIn 等)

但说实话,在智能座舱这种复杂场景下,Unittest 有点“不够用”。

我曾经踩过的坑: 用 Unittest 写座舱语音测试时,每个测试类都要继承 TestCase,然后重写 setUp 方法去连接模拟器。结果 50 个测试文件,每个文件里都有一段几乎一样的连接代码。后来改一次连接参数,改了 50 个文件……那感觉,你懂的。

4.2 Pytest:更现代、更灵活的选择

Pytest 不是标准库,需要 pip install pytest。但它带来的便利,绝对值回那几秒钟的安装时间。

核心特点:

  • 函数式测试:不需要写类,直接写函数就行
  • 自动发现测试:默认找 test_*.py 或 *_test.py 文件
  • 强大的 fixture 机制:替代 setUp/tearDown,更灵活
  • 参数化测试:一组数据跑多个用例,代码量减半
  • 丰富的插件生态:allure、xdist、ordering 等
一句话总结: Unittest 是“你按我的规矩来”,Pytest 是“我帮你把活干了”。

4.3 对比表格:一图看懂差异

对比维度 Unittest Pytest
安装方式 内置,无需安装 pip install pytest
用例组织 必须继承 TestCase 类 函数或类均可,更自由
前置后置 setUp / tearDown(固定名称) fixture(自定义名称,作用域可控)
参数化 需要 subTest 或自己写循环 @pytest.mark.parametrize,一行搞定
插件生态 较少 丰富(allure、xdist、ordering 等)
报告生成 默认文本,可扩展 pytest-html / allure,美观易读
智能座舱适用性 适合小型、简单项目 适合中大型、多场景项目

我个人习惯用 Pytest。为什么?因为智能座舱测试涉及的东西太多了——语音、导航、蓝牙、空调、仪表盘……每个模块都需要不同的前置条件。Pytest 的 fixture 能让我把这些条件拆开、复用,而不是堆在一个 setUp 里。

4.4 Pytest 核心特性详解

4.4.1 Fixture:你的测试“万能工具箱”

Fixture 是 Pytest 最强大的特性之一。说白了,它就是一段可以重复使用的代码,用来准备测试环境或清理测试数据。

基本用法:

import pytest

@pytest.fixture
def connect_car():
    # 前置:连接座舱模拟器
    print("连接座舱模拟器...")
    simulator = Simulator.connect("192.168.1.100")
    yield simulator  # 把模拟器对象传给测试函数
    # 后置:断开连接
    print("断开座舱模拟器...")
    simulator.disconnect()

def test_voice_control(connect_car):
    # connect_car 就是上面返回的 simulator 对象
    result = connect_car.send_voice_command("打开空调")
    assert result.status == "success"

Fixture 的作用域:

  • function(默认):每个测试函数都执行一次
  • class:每个测试类执行一次
  • module:每个测试文件执行一次
  • session:整个测试会话只执行一次
我的经验: 连接模拟器这种耗时操作,用 session 作用域。启动一次,所有用例共用。但要注意——如果模拟器崩溃了,后面的用例全挂。所以我会加一个心跳检测,每隔几个用例检查一次连接状态。

4.4.2 参数化:一套代码,多组数据

智能座舱测试中,经常要测同一个功能在不同条件下的表现。比如语音控制空调,温度从 16°C 到 30°C,每个温度都要测。用 Unittest 你得写 15 个几乎一样的函数。用 Pytest,一行搞定。

import pytest

@pytest.mark.parametrize("temperature, expected", [
    (16, "16°C"),
    (22, "22°C"),
    (30, "30°C"),
    (35, "30°C"),  # 超出范围,应该取最大值
])
def test_set_temperature(temperature, expected, connect_car):
    result = connect_car.set_ac_temperature(temperature)
    assert result.display == expected

你想想看,如果不用参数化,这 4 个用例你得写 4 个函数。而且如果后面要加一个 18°C 的用例,参数化只需要加一行数据,非参数化你得复制粘贴整个函数。

4.4.3 插件生态:让测试更强大

Pytest 的插件系统,是我离不开它的另一个原因。这里列几个我在座舱测试中常用的:

插件名 用途 安装命令
pytest-html 生成 HTML 格式的测试报告 pip install pytest-html
pytest-xdist 并行执行测试,节省时间 pip install pytest-xdist
pytest-ordering 控制测试用例的执行顺序 pip install pytest-ordering
pytest-rerunfailures 失败用例自动重跑 pip install pytest-rerunfailures
allure-pytest 集成 Allure 报告,更美观 pip install allure-pytest
并行执行小技巧: 用 pytest -n 4 启动 4 个进程并行跑。我试过,原来跑 30 分钟的座舱全量回归测试,缩短到 8 分钟。但要注意——如果多个用例同时操作同一个资源(比如同时设置空调温度),可能会冲突。建议按模块分组,不同模块并行,同模块串行。

4.5 框架搭建实战:从零开始搭一个座舱测试框架

光说不练假把式。下面我带你搭一个最小可用的智能座舱测试框架。别怕,就几个文件。

4.5.1 项目结构

smart_cockpit_test/
├── conftest.py          # 全局 fixture 配置
├── test_voice.py        # 语音测试
├── test_navigation.py   # 导航测试
├── test_ac.py           # 空调测试
├── utils/
│   ├── __init__.py
│   └── simulator.py     # 模拟器连接工具
├── data/
│   └── test_data.json   # 测试数据
└── reports/             # 测试报告输出目录

4.5.2 conftest.py:全局 fixture 集中管理

import pytest
from utils.simulator import Simulator

@pytest.fixture(scope="session")
def simulator():
    """全局模拟器连接,整个测试只连接一次"""
    sim = Simulator.connect("192.168.1.100")
    yield sim
    sim.disconnect()

@pytest.fixture
def reset_ac(simulator):
    """每个空调测试前,把空调重置到默认状态"""
    simulator.set_ac_temperature(24)
    simulator.set_ac_fan_speed(2)
    yield
    # 测试结束后不清理,留给下一个 fixture 处理

4.5.3 一个完整的测试文件:test_ac.py

import pytest

class TestAC:
    """空调功能测试"""

    @pytest.mark.parametrize("temp, expected", [
        (18, "18°C"),
        (25, "25°C"),
        (32, "30°C"),  # 上限 30
    ])
    def test_set_temperature(self, simulator, reset_ac, temp, expected):
        """测试设置空调温度"""
        result = simulator.set_ac_temperature(temp)
        assert result.display == expected

    def test_ac_off(self, simulator):
        """测试关闭空调"""
        simulator.turn_off_ac()
        status = simulator.get_ac_status()
        assert status == "off"

4.5.4 运行测试

# 运行所有测试
pytest

# 运行空调模块测试,生成 HTML 报告
pytest test_ac.py --html=reports/ac_report.html

# 并行运行(4 进程)
pytest -n 4

# 失败重跑(最多重跑 2 次)
pytest --reruns 2
我的习惯: 在项目根目录放一个 pytest.ini 配置文件,把常用参数写进去,这样每次只要敲 pytest 就行。
# pytest.ini
[pytest]
addopts = -v --html=reports/report.html --self-contained-html
testpaths = tests
python_files = test_*.py

4.6 知识体系总览

下面这张图,帮你把本章的核心逻辑串起来。从框架选型到核心特性,再到实战搭建,一条线走通。

智能座舱测试框架选型与搭建知识体系 框架选型 Pytest vs Unittest 选型结论 智能座舱场景推荐 Pytest Pytest 三大核心特性 Fixture 前置/后置操作 作用域:function/session 参数化 一组数据跑多个用例 @pytest.mark.parametrize 插件生态 html / xdist / ordering rerunfailures / allure 框架搭建实战(4 个关键步骤) 1. 项目结构 2. conftest.py 3. 编写测试用例 4. 运行与报告 从选型到搭建,一条线走通。核心:用 Pytest 的 fixture + 参数化 + 插件,快速搭建可维护的座舱测试框架
本章核心收获:
  • Unittest 适合小项目,Pytest 适合智能座舱这种复杂场景
  • Fixture 是 Pytest 的灵魂,学会用它管理前置后置操作
  • 参数化能让你用一套代码覆盖多组数据,减少重复劳动
  • 插件生态让测试更高效:并行、重跑、报告生成一步到位
  • 实战搭建:conftest.py + 测试文件 + pytest.ini,一个最小框架就出来了