恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Hermes自动化测试技能(1):用Jest/Pytest/Mocha搭建可复用的测试骨架
首页
资讯中心
/
Hermes自动化测试技能(1):用Jest/Pytest/Mocha搭建可复用的测试骨架
Hermes自动化测试技能(1):用Jest/Pytest/Mocha搭建可复用的测试骨架
发布时间:2026/10/11 20:38:23
1. Hermes 自动化测试骨架到底解决什么问题Hermes 自动化测试技能这个系列我想从最容易被忽略、但后面最省时间的一步讲起搭骨架。很多人第一次给 Hermes 相关项目写测试都是直接新建一个test.js或者test_xxx.py把用例堆进去跑通一次就完事。等到用例涨到几十个、需要区分单元测试和集成测试、需要 mock 外部依赖、需要在 CI 里分阶段跑的时候才发现目录乱、配置散、命令记不住改一个公共断言要动十几个文件。所谓可复用的测试骨架说白了就是三件事目录结构固定下来、配置文件集中管理、运行命令标准化。它不解决“测试写得对不对”但解决“测试放在哪、怎么跑、怎么复用”。对已经有 Jest、Pytest 或 Mocha 基础的开发者来说骨架搭好之后写用例就是往固定位置填内容心智负担会小很多。这篇面向的是已经会写基础断言、但还没系统组织过测试工程的开发者。我会给出三套可直接复制的目录结构和配置分别对应 Jest、Pytest、Mocha然后挑其中一套完整演示从写用例到断言通过的过程。Hermes 项目里常见的模块划分、外部服务调用、异步逻辑都会在骨架层面预留好位置。先明确一个判断标准什么样的骨架算合格。我的标准是四条。第一新增一个测试文件不需要改任何配置放进目录就能被识别。第二单元测试和集成测试能用不同命令分开跑。第三公共的 mock、fixture、断言封装有统一入口。第四本地跑和 CI 跑用的是同一套命令不靠人肉记忆参数。下面所有结构都围绕这四条设计。如果你现在项目里只有一个tests文件夹、里面平铺着所有用例那这篇正好适合你。不需要推倒重来按后面的结构迁移即可。Hermes 自动化测试技能后续几篇会讲用例生成、覆盖率、CI 集成这一篇是地基。2. TaoToken 前置准备把模型能力接进测试工作流在讲具体骨架之前先说清楚为什么测试工程里会用到 TaoToken。Hermes 自动化测试技能里有一部分场景是用模型辅助生成用例、补断言、分析失败原因这些能力需要一个稳定的模型调用入口。TaoToken 提供的就是这个入口兼容主流模型协议配置方式和常见 SDK 一致。你需要准备三样东西Base URL、API Key、Model ID。Base URL 用https://taotoken.net/api注意这个地址不带任何查询参数。API Key 在控制台创建Model ID 按你实际要用的模型填。这三样在后面的配置片段里会反复出现建议先记下来。创建 Key 的入口在控制台的 API Keys 页面地址是https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。进去之后新建一个 Key复制保存。注意 Key 只在创建时完整显示一次丢了就重新建。如果你只是想先验证模型能不能通可以用模型对话页面直接试地址是https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。在页面上选模型、填 Key、发一句话能返回内容就说明链路没问题。这一步建议在写测试代码之前做避免后面把网络问题和代码问题混在一起排查。对于长期做编码和 Agent 场景的可以看 Coding Plan地址是https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content里面有各语言的调用示例。这里要强调一点TaoToken 是模型调用入口不是测试框架也不替代 Jest、Pytest、Mocha。测试骨架本身不依赖它只有当你需要模型辅助生成或分析时才用到。所以下面三套骨架你可以先不接模型纯手工写用例跑通再按需接入。配置方式上我建议把 Base URL、Key、Model ID 放在环境变量里不要硬编码进测试文件。原因很简单测试代码会进版本库Key 不能进。后面每套骨架我都会给出对应的环境变量读取方式。3. 三套可复制配置Jest、Pytest、Mocha 骨架这一节是全文的核心给出三套完整可复制的配置。每套都包含目录结构、配置文件、运行命令。你可以只挑自己项目用的那套也可以三套对照看设计思路。3.1 Jest 骨架Node / TypeScript 项目目录结构如下hermes-project/ ├── src/ │ └── hermes/ │ ├── client.ts │ └── parser.ts ├── tests/ │ ├── unit/ │ │ └── parser.test.ts │ ├── integration/ │ │ └── client.test.ts │ ├── fixtures/ │ │ └── sample-response.json │ └── setup/ │ └── jest.setup.ts ├── jest.config.ts ├── jest.unit.config.ts ├── jest.integration.config.ts └── package.json基础配置jest.config.tsimport type { Config } from jest; const baseConfig: Config { preset: ts-jest, testEnvironment: node, roots: [rootDir/tests], setupFilesAfterEnv: [rootDir/tests/setup/jest.setup.ts], moduleNameMapper: { ^hermes/(.*)$: rootDir/src/hermes/$1, }, collectCoverageFrom: [src/**/*.{ts,js}], coverageDirectory: coverage, }; export default baseConfig;单元测试专用配置jest.unit.config.tsimport type { Config } from jest; import baseConfig from ./jest.config; const config: Config { ...baseConfig, testMatch: [rootDir/tests/unit/**/*.test.ts], }; export default config;集成测试专用配置jest.integration.config.tsimport type { Config } from jest; import baseConfig from ./jest.config; const config: Config { ...baseConfig, testMatch: [rootDir/tests/integration/**/*.test.ts], testTimeout: 30000, }; export default config;package.json里的脚本{ scripts: { test: jest, test:unit: jest --config jest.unit.config.ts, test:integration: jest --config jest.integration.config.ts, test:coverage: jest --coverage } }tests/setup/jest.setup.ts里放全局钩子和环境变量读取process.env.TAOTOKEN_BASE_URL process.env.TAOTOKEN_BASE_URL || https://taotoken.net/api; beforeAll(() { if (!process.env.TAOTOKEN_API_KEY) { console.warn(TAOTOKEN_API_KEY 未设置涉及模型调用的用例将跳过); } });这套结构的关键点roots限定在tests下testMatch在子配置里收窄所以新增文件只要放进unit或integration就会被自动识别不用改配置。moduleNameMapper让测试里可以用hermes/parser这种别名导入源码路径清晰。3.2 Pytest 骨架Python 项目目录结构hermes-project/ ├── src/ │ └── hermes/ │ ├── client.py │ └── parser.py ├── tests/ │ ├── unit/ │ │ └── test_parser.py │ ├── integration/ │ │ └── test_client.py │ ├── fixtures/ │ │ └── sample_response.json │ └── conftest.py ├── pytest.ini └── pyproject.tomlpytest.ini配置[pytest] testpaths tests python_files test_*.py python_classes Test* python_functions test_* markers unit: 单元测试 integration: 集成测试 addopts -ra --strict-markerstests/conftest.py放公共 fixtureimport os import json import pytest from pathlib import Path FIXTURE_DIR Path(__file__).parent / fixtures pytest.fixture(scopesession) def taotoken_config(): return { base_url: os.getenv(TAOTOKEN_BASE_URL, https://taotoken.net/api), api_key: os.getenv(TAOTOKEN_API_KEY, ), model_id: os.getenv(TAOTOKEN_MODEL_ID, ), } pytest.fixture def sample_response(): with open(FIXTURE_DIR / sample_response.json, encodingutf-8) as f: return json.load(f)运行命令pytest -m unit pytest -m integration pytest --covsrc/hermes --cov-reportterm-missingPytest 的骨架优势在于conftest.py自动发现fixture 按目录层级生效。tests/unit/conftest.py里定义的 fixture 只对单元测试可见集成测试不会误用。markers配合-m参数实现分组运行比按目录更灵活。3.3 Mocha 骨架Node 项目偏轻量目录结构hermes-project/ ├── src/ │ └── hermes/ │ ├── client.js │ └── parser.js ├── test/ │ ├── unit/ │ │ └── parser.spec.js │ ├── integration/ │ │ └── client.spec.js │ ├── fixtures/ │ │ └── sample-response.json │ └── setup.js ├── .mocharc.json └── package.json.mocharc.json{ require: [test/setup.js], spec: [test/**/*.spec.js], timeout: 10000, recursive: true }test/setup.jsprocess.env.TAOTOKEN_BASE_URL process.env.TAOTOKEN_BASE_URL || https://taotoken.net/api; global.expect require(chai).expect;package.json脚本{ scripts: { test: mocha, test:unit: mocha test/unit/**/*.spec.js, test:integration: mocha test/integration/**/*.spec.js --timeout 30000 } }Mocha 本身不带断言库所以setup.js里挂了 chai 的expect到全局。recursive让子目录自动递归。集成测试单独加长 timeout因为涉及外部调用。三套骨架的共同设计单元和集成分离、fixture 集中、setup 统一、命令标准化。你可以按项目语言选一套也可以混用比如前端 Jest、后端 Pytest。4. 验证请求从写用例到断言通过这一节用 Jest 骨架完整走一遍从写第一个用例到看到绿色通过。其他两套逻辑一致只是语法不同。先写一个被测函数。src/hermes/parser.tsexport interface HermesMessage { role: string; content: string; } export function parseHermesResponse(raw: string): HermesMessage[] { if (!raw || raw.trim() ) { throw new Error(响应内容为空); } const data JSON.parse(raw); if (!Array.isArray(data.messages)) { throw new Error(响应格式不正确缺少 messages 数组); } return data.messages.map((m: any) ({ role: String(m.role || unknown), content: String(m.content || ), })); }写单元测试tests/unit/parser.test.tsimport { parseHermesResponse } from hermes/parser; describe(parseHermesResponse, () { it(正常解析 messages 数组, () { const raw JSON.stringify({ messages: [ { role: user, content: 你好 }, { role: assistant, content: 你好有什么可以帮你 }, ], }); const result parseHermesResponse(raw); expect(result).toHaveLength(2); expect(result[0].role).toBe(user); expect(result[1].content).toContain(有什么可以帮你); }); it(空内容抛出错误, () { expect(() parseHermesResponse()).toThrow(响应内容为空); }); it(缺少 messages 字段抛出错误, () { const raw JSON.stringify({ data: [] }); expect(() parseHermesResponse(raw)).toThrow(缺少 messages 数组); }); });运行npm run test:unit预期输出PASS tests/unit/parser.test.ts parseHermesResponse ✓ 正常解析 messages 数组 ✓ 空内容抛出错误 ✓ 缺少 messages 字段抛出错误 Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total到这里第一个用例就跑通了。接下来演示集成测试怎么用 fixture 和模型配置。tests/fixtures/sample-response.json{ messages: [ { role: user, content: 生成一个测试用例 }, { role: assistant, content: 好的这是一个示例用例 } ] }tests/integration/client.test.tsimport fs from fs; import path from path; import { parseHermesResponse } from hermes/parser; describe(Hermes 响应解析集成, () { it(从 fixture 文件读取并解析, () { const fixturePath path.join(__dirname, ../fixtures/sample-response.json); const raw fs.readFileSync(fixturePath, utf-8); const result parseHermesResponse(raw); expect(result).toHaveLength(2); expect(result[0].role).toBe(user); }); it(模型配置从环境变量读取, () { const baseUrl process.env.TAOTOKEN_BASE_URL; expect(baseUrl).toBe(https://taotoken.net/api); }); });运行npm run test:integration如果TAOTOKEN_BASE_URL没设置setup.ts里已经兜底成默认值所以第二个用例能过。这就是骨架的价值环境变量读取集中在 setup用例里不用重复写。如果你要真正调用模型验证链路可以在集成测试里加一个真实请求但建议用环境变量控制开关避免 CI 里每次都打真实接口const runLive process.env.RUN_LIVE_TEST 1; (runLive ? it : it.skip)(真实调用模型返回内容, async () { const res await fetch(${process.env.TAOTOKEN_BASE_URL}/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${process.env.TAOTOKEN_API_KEY}, }, body: JSON.stringify({ model: process.env.TAOTOKEN_MODEL_ID, messages: [{ role: user, content: 回复 ok }], }), }); expect(res.status).toBe(200); });本地想跑真实请求时RUN_LIVE_TEST1 npm run test:integration这样默认跳过需要时手动开既验证了链路又不拖慢日常测试。5. 常见报错排查401、local proxy failed、reading choices、OAuth这一节对照真实会遇到的报错给出定位思路。这些报错在 Hermes 自动化测试接入模型能力时出现频率最高。401 Unauthorized。最常见原因是 Key 没读到或读错。先确认环境变量名和代码里读的一致比如代码读TAOTOKEN_API_KEY你设的是TAOTOKEN_KEY那就读不到。其次确认 Key 没有多余空格复制时容易带上换行。再确认请求头格式是Authorization: Bearer key少了Bearer前缀也会 401。排查命令echo $TAOTOKEN_API_KEY | head -c 8看前几位是否正常不要打印完整 Key。local proxy failed。这个报错通常出现在请求根本没发出去被本地网络层拦了。检查你的运行环境有没有设置HTTP_PROXY/HTTPS_PROXY环境变量如果有但代理不可用请求会失败。测试环境建议清掉这些变量unset HTTP_PROXY HTTPS_PROXY另外确认 Base URL 拼写正确是https://taotoken.net/api不要多加路径或斜杠。reading choices或Cannot read properties of undefined (reading choices)。这是解析响应时choices不存在。原因一般是响应体不是预期的模型返回结构可能是错误响应被当成正常响应解析了。排查方法先把原始响应打印出来。const data await res.json(); console.log(JSON.stringify(data, null, 2));如果看到的是{ error: ... }说明请求本身失败了先解决失败原因再谈解析。测试里建议加一层判断if (!data.choices) { throw new Error(响应缺少 choices: ${JSON.stringify(data)}); }OAuth 相关报错。如果你用的是需要 OAuth 的客户端比如某些 CLI 工具报错可能提示 token 过期或未授权。这类场景下确认三件套是否齐全Base URL、Key、Model ID。以 Codex 的auth.json为例配置结构大致如下{ base_url: https://taotoken.net/api, api_key: 你的 Key, model: 你的 Model ID }三个字段缺一不可。Base URL 不带查询参数Key 用控制台创建的Model ID 按实际模型填。如果用的是 Claude Code 这类工具配置项名称可能不同但核心还是这三样。接入文档里有各客户端的完整示例地址是https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。再补一个测试工程本身的常见坑Jest 里testMatch写错导致用例不被识别表现为No tests found。检查testMatch的 glob 是否匹配你的文件路径。Pytest 里 fixture 找不到多半是conftest.py放错层级。Mocha 里describe is not defined是没在 setup 里引入或没配require。排查顺序建议固定先确认请求有没有发出去看网络层报错再确认响应状态码401 还是 200再确认响应结构有没有 choices最后才是业务断言。按这个顺序大部分问题能在前三步定位。6. 把骨架用起来下一步怎么接骨架搭好之后日常写测试就是往unit和integration目录填文件公共逻辑往fixtures和setup放。新增用例不需要动配置这是判断骨架是否合格的最直接标准。如果你想让模型辅助生成用例可以在测试工程里加一个脚本读取源码文件调用模型生成测试草稿人工审核后放进对应目录。这一步用到的模型调用配置就是前面说的三件套。模型对话页面可以先手动试效果地址是https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。长期做这类自动化Coding Plan 会更合适地址是https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。Key 管理和接入文档分别在https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content和https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。建议先把 Key 建好、用模型对话验证一次再回到测试工程里接。最后给一个实用建议骨架里的运行命令写进package.json或Makefile团队统一用npm run test:unit这种命令不要各自记参数。CI 配置里直接复用同一套命令本地和线上行为一致能省掉大量“在我机器上是好的”这类问题。下一篇会讲用例生成和覆盖率骨架是那一步的前提。