Python+pytest+plum-voice构建RPA语音测试自动化框架实战

发布时间:2026/7/5 9:37:21
Python+pytest+plum-voice构建RPA语音测试自动化框架实战 1. 项目概述当RPA遇上语音测试自动化如果你正在用Python和pytest做自动化测试并且你的业务里涉及到语音交互——比如一个需要接听电话、根据语音提示按键、或者识别语音指令的RPA机器人那你肯定遇到过测试上的麻烦。传统的测试方法要么得你真人拿着电话一遍遍打要么就得模拟一堆复杂的网络请求和音频流既费时又容易出错。这个项目要解决的就是如何把Python、pytest和一个叫plum-voice的工具或者类似的语音服务/模拟器拧在一起搭建一套能自动执行、能重复验证、还能生成清晰报告的语音测试流水线。简单说它能让你的测试脚本“开口说话”和“听懂人话”自动完成那些需要语音交互的测试场景。比如测试一个银行的电话客服IVR交互式语音应答系统脚本可以自动拨打测试号码模拟用户按“1”查询余额、按“2”转人工并验证系统的语音播报是否正确。再比如测试一个智能家居的语音助手脚本可以模拟说出“打开客厅灯”的指令并检查灯控API是否被正确调用。这不仅仅是省去了人工操作更重要的是它把语音交互这种非结构化的、依赖音频的测试变成了结构化的、可断言Assert的代码逻辑让测试结果变得确定和可追踪。2. 核心架构与工具选型解析2.1 为什么是Python pytest plum-voice这个组合首先得拆解一下这个技术栈每个部分扮演的角色。Python是胶水也是主力。它的生态里有大量用于HTTP请求如requests、音频处理如pydub,speech_recognition、以及自动化控制这正是RPA的核心的库语法简洁写测试脚本和业务逻辑都非常高效。pytest是测试框架的“当前最佳实践”。它比Python自带的unittest更灵活夹具fixture机制强大断言写法直观直接用assert插件生态丰富比如pytest-html生成报告pytest-xdist并行测试。在语音测试这种场景里我们经常需要为每个测试用例准备特定的测试音频文件、模拟的语音服务端点或者清理测试后的临时文件pytest的fixture能把这些准备工作管理得井井有条。plum-voice在这里是一个关键组件。根据网络上的信息它很可能是一个提供语音识别ASR和语音合成TTSAPI的服务或者是用于模拟IVR系统的工具。在自动化测试的语境下我们主要用它做两件事一是生成测试语音比如把一段文本“您的余额是100元”合成为.wav文件用于模拟系统播报二是识别语音比如把一段模拟用户说话的音频文件转换成文本“查询余额”用于验证或作为下一步操作的输入。如果plum-voice是一个云服务那么我们的测试代码就需要通过其API与之交互如果它是一个本地库或模拟器那我们就可以直接在测试环境中调用。这个组合的优势在于解耦和可模拟。理想的架构是你的核心业务逻辑比如处理“查询余额”这个意图是独立于具体的语音服务Twilio、plum-voice、阿里云等的。这样在测试时你就可以用一个模拟Mock对象或者一个测试专用的语音服务沙盒来替换真实的plum-voice生产环境API。这避免了测试时产生费用、拨打真实电话或者受限于外部服务的稳定性和速率限制。2.2 项目整体设计思路一个健壮的语音测试自动化框架应该遵循“分层测试”和“依赖注入”的思想。不要把调用plum-voice API的代码和你的业务逻辑硬编码在一起。抽象层Interface/Client首先定义一个抽象的“语音服务客户端”接口。这个接口声明一些关键方法比如text_to_speech(text: str) - bytes返回音频数据和speech_to_text(audio_data: bytes) - str。然后为plum-voice实现一个具体的客户端类。这个类内部处理HTTP请求、认证、错误重试等细节。业务逻辑层你的RPA流程或应用核心逻辑。它只依赖上面那个抽象的接口而不是具体的plum-voice客户端。它调用speech_to_text来理解用户指令处理业务再调用text_to_speech生成回复。测试层单元测试针对业务逻辑层。使用unittest.mock库创建一个Mock对象来冒充语音服务接口。你可以预设这个Mock对象的行为例如当调用speech_to_speech时直接返回你预设的文本“查询余额”。这样你可以在完全隔离的情况下测试业务逻辑是否正确运行速度极快。集成测试/端到端E2E测试这部分就是本项目的重点。我们会使用真实的或测试环境的plum-voice服务或者一个专门用于测试的语音交互模拟器。测试脚本会扮演一个“机器人用户”执行一个完整的流程发送音频、接收音频、识别内容、做出判断。测试数据管理准备一批高质量的测试音频文件至关重要。包括清晰的标准指令音频用于正向用例。带有口音、噪音的音频用于鲁棒性测试。边界案例音频语速极快或极慢。预期的系统回复音频用于对比验证。这些文件应该被妥善组织比如按测试用例目录存放并通过pytest fixture来按需加载。注意直接测试真实语音识别的准确率如字错误率通常不是UI自动化测试的重点那更多是算法测试的范畴。我们的自动化测试更关注流程和意图给定一段标准测试音频系统是否能识别出正确的意图Intent或关键信息Entity并触发正确的业务流程。3. 环境搭建与核心依赖安装工欲善其事必先利其器。下面我们来一步步搭建这个自动化测试环境。我会假设你是在一个相对干净的Python项目环境中操作。3.1 Python环境与包管理首先确保你安装了Python 3.8或更高版本。我强烈推荐使用虚拟环境Virtual Environment来隔离项目依赖避免包冲突。# 在项目根目录下 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # Linux/Mac: source venv/bin/activate激活后你的命令行提示符前会出现(venv)字样。接下来我们使用pip安装核心依赖。创建一个requirements.txt文件是个好习惯。# requirements.txt pytest7.0.0 pytest-html3.0.0 # 用于生成漂亮的HTML报告 pytest-xdist3.0.0 # 可选用于并行运行测试加快速度 requests2.28.0 # 用于调用plum-voice或其他HTTP API pydub0.25.0 # 用于音频文件的处理和格式转换 SpeechRecognition3.10.0 # 可选如果需要在测试中做本地音频识别验证然后安装它们pip install -r requirements.txtpydub处理音频非常方便但它的底层依赖ffmpeg。你需要单独安装ffmpegWindows从官网下载编译好的二进制文件将bin目录添加到系统PATH。Macbrew install ffmpegLinux (Ubuntu/Debian)sudo apt install ffmpeg3.2 配置plum-voice访问如果plum-voice是一个云服务你通常需要一个API Key或Token。绝对不要把密钥硬编码在代码里更不要上传到GitHub。标准做法是使用环境变量。在项目根目录创建一个.env文件确保它在.gitignore里PLUM_VOICE_API_KEYyour_test_environment_api_key_here PLUM_VOICE_BASE_URLhttps://api-test.plumvoice.com/v1 # 示例请替换为实际测试环境地址在Python代码中使用python-dotenv库来加载先pip install python-dotenv# conftest.py 或配置模块中 from dotenv import load_dotenv import os load_dotenv() # 加载 .env 文件中的变量到环境变量 PLUM_VOICE_API_KEY os.getenv(PLUM_VOICE_API_KEY) PLUM_VOICE_BASE_URL os.getenv(PLUM_VOICE_BASE_URL) if not PLUM_VOICE_API_KEY: raise ValueError(PLUM_VOICE_API_KEY 环境变量未设置)3.3 项目目录结构规划一个清晰的结构能让测试代码更易维护。我建议的目录结构如下rpa_voice_test_project/ ├── .env # 环境变量本地不上传 ├── .gitignore ├── requirements.txt ├── conftest.py # pytest全局配置文件放公共fixture ├── tests/ # 所有测试用例 │ ├── __init__.py │ ├── unit/ # 单元测试 │ │ ├── test_business_logic.py │ │ └── ... │ └── integration/ # 集成/端到端测试 │ ├── __init__.py │ ├── conftest.py # 集成测试特有的fixture │ ├── test_ivr_balance.py │ └── ... ├── src/ # 源代码 │ ├── __init__.py │ ├── voice_client.py # plum-voice客户端抽象与实现 │ ├── business_logic.py # 核心RPA业务逻辑 │ └── ... └── test_data/ # 测试用的音频文件等资源 ├── audio_input/ │ ├── query_balance.wav │ ├── transfer_to_agent.wav │ └── noisy_background_query.wav └── audio_expected/ └── balance_is_100.wav4. 核心模块实现详解4.1 构建可测试的语音客户端voice_client.py这是连接我们测试代码与plum-voice服务的桥梁。我们要让它易于使用也易于在测试中被模拟。# src/voice_client.py import json from abc import ABC, abstractmethod from typing import Optional import requests from pydub import AudioSegment import io class BaseVoiceClient(ABC): 语音服务客户端抽象基类。 abstractmethod def text_to_speech(self, text: str, language: str zh-CN) - bytes: 将文本合成为音频数据。 pass abstractmethod def speech_to_text(self, audio_data: bytes, language: str zh-CN) - str: 将音频数据识别为文本。 pass class PlumVoiceClient(BaseVoiceClient): Plum-Voice服务的具体实现。 def __init__(self, base_url: str, api_key: str): self.base_url base_url.rstrip(/) self.api_key api_key self.session requests.Session() self.session.headers.update({ Authorization: fBearer {self.api_key}, Content-Type: application/json }) def text_to_speech(self, text: str, language: str zh-CN) - bytes: 调用TTS API。 注意实际API参数和端点需根据plum-voice文档调整。 url f{self.base_url}/tts payload { text: text, language: language, voice: Xiaoyan, # 示例音色 format: wav } try: response self.session.post(url, jsonpayload, timeout30) response.raise_for_status() # 如果状态码不是200抛出HTTPError # 假设API直接返回音频二进制流 return response.content except requests.exceptions.RequestException as e: # 在实际项目中这里应该有更细致的错误处理和重试逻辑 raise Exception(fTTS API调用失败: {e}) def speech_to_text(self, audio_data: bytes, language: str zh-CN) - str: 调用ASR API。 注意实际API参数和端点需根据plum-voice文档调整。 url f{self.base_url}/asr # 假设API接受multipart/form-data格式的文件上传 files {audio: (audio.wav, audio_data, audio/wav)} data {language: language} try: # 注意这里headers可能不同需要移除JSON的Content-Type response self.session.post(url, filesfiles, datadata, timeout30) response.raise_for_status() result response.json() # 假设返回格式为 {text: 识别出的文本, confidence: 0.95} return result.get(text, ) except (requests.exceptions.RequestException, json.JSONDecodeError) as e: raise Exception(fASR API调用失败: {e}) staticmethod def load_audio_file(file_path: str) - bytes: 从本地文件加载音频为字节数据。支持常见格式如wav, mp3。 audio AudioSegment.from_file(file_path) # 统一转换为单声道、16kHz、16bit的wav格式提高识别稳定性 audio audio.set_channels(1).set_frame_rate(16000).set_sample_width(2) buffer io.BytesIO() audio.export(buffer, formatwav) return buffer.getvalue()关键点解析抽象基类ABC定义了接口BaseVoiceClient。这样你的业务逻辑BusinessLogic类就可以在构造函数中接收一个BaseVoiceClient类型的参数。在测试时你可以传入一个Mock对象在生产时传入PlumVoiceClient实例。这就是依赖注入极大地提高了可测试性。错误处理真实网络调用会失败。这里做了基本的异常捕获和转换在实际项目中你需要根据plum-voice的API文档和业务需求实现更健壮的重试机制例如使用tenacity库和错误分类。音频预处理在load_audio_file方法中我们对加载的音频做了标准化处理单声道、16kHz。这是因为不同的语音识别引擎对输入音频的格式有偏好统一格式可以减少因音频格式问题导致的识别失败让测试更稳定。配置化API的URL、认证方式、请求格式JSON还是form-data都需要你根据plum-voice的实际文档来调整。上面的代码是一个通用性较强的示例。4.2 编写核心业务逻辑business_logic.py业务逻辑应该干净只关心“做什么”不关心“怎么做”比如具体怎么调用API。# src/business_logic.py class IVRService: 一个模拟的IVR业务逻辑服务。 def __init__(self, voice_client: BaseVoiceClient): self.voice_client voice_client self.balance 100 # 模拟用户余额 def process_voice_command(self, audio_input: bytes) - bytes: 处理语音输入返回语音输出。 # 1. 语音识别 text self.voice_client.speech_to_text(audio_input) print(f[DEBUG] 识别到的文本: {text}) # 测试时有用 # 2. 意图理解这里用简单的关键词匹配实际可能用NLU引擎 response_text 抱歉我没有听清您的指令。 if 余额 in text or 查询 in text: response_text f您的账户余额是{self.balance}元。 elif 人工 in text or 客服 in text: response_text 正在为您转接人工客服请稍候。 # ... 可以扩展更多意图 # 3. 语音合成 response_audio self.voice_client.text_to_speech(response_text) return response_audio def get_balance(self) - int: 获取余额用于单元测试示例。 return self.balance这个类非常简单它接收一个语音客户端然后提供了一个核心的process_voice_command方法。这就是我们集成测试要测的主要流程。4.3 设计pytest测试夹具Fixtures夹具是pytest的灵魂它能帮我们优雅地准备测试环境和数据。我们把常用的资源定义在conftest.py里这样所有测试文件都能自动使用。# tests/conftest.py import pytest from unittest.mock import Mock, MagicMock import os from src.voice_client import PlumVoiceClient, BaseVoiceClient from src.business_logic import IVRService pytest.fixture def mock_voice_client(): 创建一个模拟的语音客户端用于单元测试。 client Mock(specBaseVoiceClient) # 确保Mock对象符合接口规范 # 预设speech_to_text的行为当传入任意音频都返回“查询余额” client.speech_to_text.return_value 查询余额 # 预设text_to_speech的行为当传入“您的账户余额是100元。”返回一个模拟的音频字节串 client.text_to_speech.return_value bfake_audio_data return client pytest.fixture def real_voice_client(): 创建连接真实测试环境plum-voice的客户端用于集成测试。 如果没配置环境变量则跳过相关测试。 api_key os.getenv(PLUM_VOICE_API_KEY) base_url os.getenv(PLUM_VOICE_BASE_URL) if not api_key or not base_url: pytest.skip(未配置PLUM_VOICE_API_KEY或PLUM_VOICE_BASE_URL环境变量跳过集成测试) return PlumVoiceClient(base_urlbase_url, api_keyapi_key) pytest.fixture def test_audio_dir(): 返回测试音频文件目录的绝对路径。 return os.path.join(os.path.dirname(__file__), .., test_data, audio_input) pytest.fixture def expected_audio_dir(): 返回预期音频文件目录的绝对路径。 return os.path.join(os.path.dirname(__file__), .., test_data, audio_expected)夹具使用心得mock_voice_client在单元测试中我们完全不依赖外部服务。这个Mock对象的行为是完全可控的测试执行速度极快且不受网络波动影响。real_voice_client这个夹具包含了条件判断。如果跑测试的人没有配置环境变量比如在CI/CD流水线的早期阶段只想跑单元测试那么所有依赖这个夹具的测试用例都会被pytest.skip自动跳过而不会报错失败。这非常有用。路径处理使用os.path.join和__file__来构建路径这样无论从项目根目录还是其他位置运行pytest都能正确找到测试数据。5. 编写并运行自动化测试用例5.1 单元测试示例隔离测试业务逻辑单元测试的目标是验证IVRService内部的逻辑是否正确与语音客户端无关。# tests/unit/test_business_logic.py from src.business_logic import IVRService def test_ivr_service_balance_query(mock_voice_client): 测试IVR服务处理‘查询余额’意图的逻辑。 # 1. 准备 (Arrange) service IVRService(voice_clientmock_voice_client) fake_audio bany_audio_data # 2. 执行 (Act) # 由于mock_voice_client.speech_to_text被预设为返回“查询余额” # 所以process_voice_command内部会走到余额查询的分支 output_audio service.process_voice_command(fake_audio) # 3. 断言 (Assert) # 断言语音客户端被正确调用了两次 mock_voice_client.speech_to_text.assert_called_once_with(fake_audio) # 检查text_to_speech是否被用预期的文本来调用 # 注意这里断言的是调用参数而不是返回值 mock_voice_client.text_to_speech.assert_called_once_with(您的账户余额是100元。) # 也可以断言返回的音频数据就是我们预设的模拟数据 assert output_audio bfake_audio_data def test_get_balance(mock_voice_client): 测试简单的get_balance方法。 service IVRService(voice_clientmock_voice_client) assert service.get_balance() 100单元测试技巧使用spec在创建Mock时使用specBaseVoiceClient这样Mock对象会模仿真实接口的属性。如果你不小心调用了接口中不存在的方法Mock会抛出AttributeError这有助于在测试阶段发现代码错误。断言调用.assert_called_once_with(...)是Mock对象最强大的功能之一。它不仅能验证方法是否被调用还能验证调用时的参数是否正确。这比只检查最终返回值更能保证代码的交互逻辑无误。5.2 集成测试示例端到端语音流程测试集成测试我们要动真格的使用真实的语音服务测试环境。# tests/integration/test_ivr_balance.py import os def test_complete_ivr_balance_flow(real_voice_client, test_audio_dir, expected_audio_dir): 完整的IVR余额查询流程测试从音频输入到音频输出。 # 0. 准备服务 from src.business_logic import IVRService service IVRService(voice_clientreal_voice_client) # 1. 加载测试输入音频模拟用户说“查询余额” input_audio_path os.path.join(test_audio_dir, query_balance.wav) # 使用客户端的方法加载并预处理音频 input_audio_data real_voice_client.load_audio_file(input_audio_path) # 2. 执行核心流程 response_audio_data service.process_voice_command(input_audio_data) # 这是一个真实的音频二进制数据 assert response_audio_data is not None assert len(response_audio_data) 100 # 简单断言有数据返回 # 3. 可选但推荐验证响应音频的内容 # 方法A将响应音频再次识别为文本进行断言 recognized_response_text real_voice_client.speech_to_text(response_audio_data) print(f[INFO] 系统回复的识别文本: {recognized_response_text}) # 断言回复中包含关键信息 assert 余额 in recognized_response_text or 100 in recognized_response_text # 方法B与预存的“正确回复”音频文件进行声学特征对比更复杂更精确 # 可以使用pydub计算音频的RMS音量或频谱进行简单比对但通常文本比对足够。 # expected_audio_path os.path.join(expected_audio_dir, balance_is_100.wav) # expected_audio_data real_voice_client.load_audio_file(expected_audio_path) # ... 音频比对逻辑 ... def test_speech_to_text_accuracy(real_voice_client, test_audio_dir): 单独测试语音识别的准确率针对关键测试用例。 audio_path os.path.join(test_audio_dir, query_balance.wav) audio_data real_voice_client.load_audio_file(audio_path) recognized_text real_voice_client.speech_to_text(audio_data, languagezh-CN) # 这里我们期望一个精确或模糊的匹配 # 精确断言适用于非常清晰的测试音频 # assert recognized_text 查询余额 # 模糊断言更实用使用in或正则表达式 assert 查询 in recognized_text or 余额 in recognized_text # 你也可以记录识别结果和置信度用于长期监控服务质量集成测试要点测试稳定性集成测试依赖外部服务可能不稳定。除了使用测试环境的API Key还应该在测试中增加合理的超时和容错。对于非关键路径的失败可以考虑标记测试为pytest.mark.flaky(reruns2)需要pytest-rerunfailures插件自动重试。断言策略直接断言合成的音频二进制数据是否相等几乎不可能因为每次合成可能有微小差异。更可行的策略是文本比对将输出音频再识别成文本然后断言文本内容。这是最常用、最有效的方法。关键信息提取不要求全文匹配只检查回复中是否包含“余额”、“100元”等关键信息。音频属性比对在极少数需要验证音频本身如播报的金额数字是否正确的场景可以先将音频识别为文本或者使用专门的语音验证库但复杂度高。测试数据query_balance.wav这个文件的质量至关重要。最好是在安静的录音环境下用清晰的普通话录制。文件大小不宜过大3-5秒为宜。5.3 运行测试并生成报告在项目根目录下运行以下命令# 运行所有测试 pytest # 运行特定目录下的测试如只跑集成测试 pytest tests/integration/ # 运行包含特定关键词的测试 pytest -k balance # 详细输出显示每个测试用例的名称和状态 pytest -v # 生成HTML测试报告需要pytest-html pytest --htmlreport.html --self-contained-html生成的report.html报告会非常直观包含通过率、失败详情、错误日志等非常适合在CI/CD流水线中归档或发送给团队查看。6. 高级技巧与实战问题排查6.1 如何处理测试中的异步与超时真实的语音交互可能有网络延迟。如果你的语音客户端或业务逻辑是异步的使用了asynciopytest也完美支持。你需要安装pytest-asyncio插件并使用pytest.mark.asyncio装饰器。import pytest import asyncio from unittest.mock import AsyncMock # Python 3.8 pytest.mark.asyncio async def test_async_voice_processing(): mock_async_client AsyncMock(specBaseVoiceClient) mock_async_client.speech_to_text.return_value 测试 # ... 测试异步业务逻辑 ...对于网络请求务必设置超时。在PlumVoiceClient的requests调用中我们已经设置了timeout30。在测试中如果某个操作特别慢可以使用pytest.mark.timeout装饰器为单个测试设置最大执行时间需要pytest-timeout插件。6.2 如何管理测试数据与状态语音测试往往需要大量的音频文件。管理它们的最佳实践是版本化将小的、关键的测试音频文件如query_balance.wav放在test_data/目录下随代码一起用Git管理。外部化对于大型音频数据集可以考虑使用云存储如S3并在测试开始时按需下载到本地缓存。可以在conftest.py中写一个夹具来处理这个下载和清理逻辑。状态隔离每个集成测试应该独立不依赖上一个测试留下的状态。如果测试会改变服务端状态比如创建了一个测试用的语音合成任务一定要在测试结束时通过fixture的yield或finalizer进行清理。pytest.fixture def temporary_voice_profile(real_voice_client): 创建一个临时的语音配置测试后删除。 profile_id real_voice_client.create_profile(test_temp_voice) yield profile_id # 测试结束后执行清理 real_voice_client.delete_profile(profile_id)6.3 常见问题与排查清单在实际操作中你肯定会遇到各种问题。下面这个表格整理了一些典型情况问题现象可能原因排查步骤与解决方案单元测试通过集成测试失败1. 环境变量未正确设置。2. 测试环境API服务不可用或网络不通。3. 音频文件格式不符合API要求。1. 检查.env文件或CI/CD环境变量。2. 用curl或Postman手动调用一下plum-voice测试API看是否正常。3. 使用pydub检查音频文件的声道、采样率、位深并按照load_audio_file中的方法进行标准化转换。语音识别结果为空或完全错误1. 音频质量差有噪音、音量太小。2. 语言参数设置错误。3. 音频编码格式不被支持。1. 确保测试音频是在安静环境下清晰录制的。可以用Audacity等工具查看波形。2. 确认调用speech_to_text时传递的language参数是否正确如zh-CN。3. 尝试将音频转换为标准的PCM WAV格式单声道16kHz16bit再测试。测试执行速度慢1. 每个测试都重新建立HTTP连接。2. 音频文件较大加载和传输耗时。3. 网络延迟高。1. 在PlumVoiceClient中使用requests.Session()保持连接复用。2. 压缩测试音频文件或使用更短的音频片段。3. 考虑将集成测试标记为“慢速测试”pytest.mark.slow在日常开发中默认不运行只在CI或 nightly build 中运行。生成的HTML报告没有日志print语句的输出默认不会进入pytest-html报告。在测试中使用Python的logging模块或者使用pytest的s参数-s禁用输出捕获但这样终端会乱。更好的办法是使用caplogfixture来捕获日志并断言。Mock对象没有按预期被调用业务逻辑没有执行到调用Mock方法的分支。在测试中打印调试信息或使用Mock的call_args_list属性来查看所有调用记录检查传入的参数是否符合预期。确保你的Mock预设return_value和业务逻辑中的调用是匹配的。6.4 集成到CI/CD流水线要让自动化测试发挥最大价值必须把它集成到持续集成/持续部署流程中。环境准备在CI服务器如Jenkins、GitHub Actions、GitLab CI上配置好PLUM_VOICE_API_KEY等敏感信息作为加密的环境变量。步骤编排# GitHub Actions 示例 .github/workflows/test.yml jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Set up Python uses: actions/setup-pythonv4 with: { python-version: 3.10 } - name: Install dependencies run: pip install -r requirements.txt - name: Install ffmpeg run: sudo apt-get update sudo apt-get install -y ffmpeg - name: Run unit tests run: pytest tests/unit/ -v - name: Run integration tests run: pytest tests/integration/ -v --htmlintegration-report.html env: PLUM_VOICE_API_KEY: ${{ secrets.PLUM_VOICE_API_KEY }} PLUM_VOICE_BASE_URL: ${{ secrets.PLUM_VOICE_BASE_URL }} - name: Upload test report uses: actions/upload-artifactv3 with: name: integration-test-report path: integration-report.html质量门禁可以配置CI流水线只有当单元测试和集成测试的通过率达到100%或你设定的阈值时才允许代码合并Merge或部署。这套以Python和pytest为核心集成plum-voice或类似服务的语音测试自动化方案将原本手动、重复、易出错的语音测试任务转化为了可编程、可重复、可报告的自动化流程。它不仅仅是一个测试工具更是将RPA流程中“语音”这一关键交互环节进行质量保障的工程化实践。从抽象设计到具体实现从单元测试到端到端集成再到CI/CD的融入每一步都围绕着“稳定、高效、可维护”的目标。当你下次再需要测试一个会“说话”的机器人时希望这份指南能让你从容地写出第一行测试代码。