You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python中Gatttool命令相关单元测试与蓝牙连接上层函数测试方法问询

Python中Gatttool命令相关单元测试与蓝牙连接上层函数测试方法问询

我太懂这种困扰了——写了调用终端命令和硬件交互的代码,想做单元测试又怕每次都要连真实设备,而且还没法隔离代码逻辑和硬件问题。下面我结合pexpect和Python单元测试的常用技巧,给你拆解两种场景的测试方案,顺便先修正你代码里的一个小问题:

你的原代码里send函数直接访问gatt变量会触发NameError,因为connect里的gatt是局部变量,外部拿不到。先把代码调整成可正常运行的版本:

import pexpect
MAC = "00:80:41:AE:fD:7C"

def connect():
    gatt = pexpect.spawn("sudo gatttool -I")
    gatt.sendline("connect " + MAC)
    gatt.expect("Connection successful")
    return gatt  # 返回gatt实例,让上层函数能调用

def send(x):
    gatt = connect()
    gatt.sendline(f"char-write-req 0x000c {x}0")
    gatt.expect("Characteristic value was written successfully")

send("ff")

一、单元测试:隔离硬件,验证命令调用逻辑

单元测试的核心是只测你的代码逻辑,不依赖真实硬件,这里我们用Python标准库的unittest.mock(或者pytest的pytest-mock插件)来模拟pexpect的行为,完全跳过真实的gatttool调用和蓝牙连接。

比如测试connect函数,我们要验证:

  • 代码是否正确启动了gatttool -I进程
  • 是否发送了正确的连接命令
  • 是否正确等待了连接成功的响应

测试代码示例(用unittest):

import unittest
from unittest import mock
from your_script import connect, MAC  # 替换your_script为你的脚本文件名

class TestGattFunctions(unittest.TestCase):
    @mock.patch('pexpect.spawn')
    def test_connect_correctly_calls_gatttool(self, mock_spawn):
        # 模拟gatt实例的行为:expect能匹配到"Connection successful"
        mock_gatt_instance = mock_spawn.return_value
        mock_gatt_instance.expect.return_value = 0

        # 调用要测试的connect函数
        connect()

        # 验证调用是否符合预期
        mock_spawn.assert_called_once_with("sudo gatttool -I")
        mock_gatt_instance.sendline.assert_called_once_with(f"connect {MAC}")
        mock_gatt_instance.expect.assert_called_once_with("Connection successful")

再比如测试send函数,我们可以模拟connect返回一个假的gatt实例,验证send是否正确发送了写特征值的命令:

@mock.patch('your_script.connect')  # 模拟connect函数
    def test_send_correctly_writes_characteristic(self, mock_connect):
        # 创建模拟的gatt实例
        mock_gatt = mock.Mock()
        mock_connect.return_value = mock_gatt
        mock_gatt.expect.return_value = 0

        # 调用send函数
        send("ff")

        # 验证调用逻辑
        mock_connect.assert_called_once()
        mock_gatt.sendline.assert_called_once_with("char-write-req 0x000c ff0")
        mock_gatt.expect.assert_called_once_with("Characteristic value was written successfully")

这种测试的好处是:

  • 不需要真实蓝牙设备,跑测试快得很
  • 能精准定位你的代码逻辑问题,比如参数传错了、命令拼错了

二、集成测试:验证真实硬件交互

如果要确保你的代码能和真实蓝牙设备正常工作,那就要做集成测试——这时候需要测试环境里有可用的目标蓝牙设备,而且MAC地址正确。

用pytest的话,可以给这类测试加个标记,区分开单元测试,只在有硬件的环境运行:

import pytest
from your_script import send

@pytest.mark.integration
def test_send_to_real_device():
    # 这里假设测试环境已经配对好目标蓝牙设备
    send("ff")
    # 如果设备有反馈,还可以加额外验证,比如:
    # gatt = connect()
    # gatt.expect("Expected feedback from device")  # 替换成设备实际的响应

运行的时候,只跑集成测试:pytest -m integration,平时跑单元测试就跳过这些。

三、几个小提醒

  • 如果你用sudo调用gatttool,测试时可能需要权限,要么给测试进程加sudo,要么配置gatttool不需要sudo(修改udev规则)
  • 模拟的时候,别忘了处理异常场景:比如模拟expect匹配失败(返回-1),测试你的代码是否能正确处理连接失败的情况
  • 尽量不要用全局变量存gatt实例,容易导致多调用时的状态混乱,最好每次调用connect都返回新实例,或者用类来管理连接状态

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 08:24:34