
1. 为什么用 C 写 Web 自动化测试而不是 Python 或 Java看到标题可能很多人第一反应是C 也能做 Web 自动化测试这不是拿大炮打蚊子吗 说实话我在最初接触这个方向时也有同样的疑问。常规认知里Web 自动化测试的标配是 Python 的 Selenium、Playwright或者 Java 系的 RestAssuredC 在这块确实冷门。但近几年随着性能测试、高频接口回归、嵌入式 Web 服务和金融交易系统的测试需求增多C 在自动化测试领域反而成了一种非对称优势的存在。先说清楚一个概念C 做 Web 自动化测试不是拿 C 去写一堆 Selector 操作浏览器页面元素的脚本那是 Selenium 的强项你也别指望 C 能比 Python 干得更漂亮。它更适合的场景是协议层接口测试、高并发压力测试、以及对性能敏感的回放验证。换句话说凡是涉及网络通信 数据校验 高频执行的测试任务C 都值得入场。我在之前的一个交易系统中就用 C 搭建了一套 Web 接口回归框架。下单接口每天要跑 3000 多次回归用例Python 脚本跑完要 18 分钟换成 C 后压缩到了不到 4 分钟。而这样的性能收益在持续集成流水线里会直接转化为部署效率。简单来说C 做 Web 自动化测试不是替代谁而是在特定场景下碾压谁。这篇内容适合三类人对 C 有一定基础但没接触过 Web 测试的工程师想拓宽技术边界测试开发工程师想给团队补充一个高性能的接口测试方案后端或嵌入式转岗的开发者本身熟悉 C又不愿意为了测试去学一套新语言。接下来我会从环境选型、核心函数实现、场景化案例到疑难排查完整走一遍 C Web 自动化测试的落地过程。2. 环境搭建与工具链选型别在第一步就踩坑2.1 测试框架选型GoogleTest 和 Catch2 怎么选C 写自动化测试首先得决定用哪个测试框架。这不是随便挑一个的问题它直接决定你后面写用例、跑报告、接入 CI 的方式。我用过两种主流框架给出一个比较实际的对比结论对比项GoogleTestgtestCatch2断言宏丰富度丰富对容器、字符串、浮点精度的断言都方便较丰富具体场景略有差异测试发现机制手动注册 TEST/TEST_F 宏自动发现不需要 tb 文件也能识别输出报告自带 XML 输出对接 Jenkins 很顺支持 JUnit XML但需额外配置上手成本需要写 main 函数调用初始化可以只写一个 main用 TEST_CASE 直接跑高性能测试支持支持可以写压力测试支持但大型项目略微绕个人建议如果团队已经有 CI 体系优先 GoogleTest。它的 TEST_Ffixture 机制在管理共享数据时非常方便而且 XML 报告格式在 Jenkins 里几乎零配置。如果只是个人项目或快速原型Catch2 会让你少写很多样板代码它的 BDD 风格也更直观。我自己的工作流是框架用 GoogleTest断言库只用它自带的 ASSERT/EXPECT 系列HTTP 客户端用 libcurl 封装JSON 解析统一用 nlohmann/json。2.2 HTTP 客户端选型libcurl 是主流但封装很关键libcurl 是 C Web 自动化测试的基石。它支持 HTTP/HTTPS、GET/POST/PUT/DELETE还支持 Cookie、Basic 认证、代理、超时设置几乎覆盖所有常见的 Web 测试需求。但有个问题libcurl 的 API 太底层了直接用 original API 写脚本会非常啰嗦。比如一个简单的 GET 请求你需要初始化句柄、设置 URL、设置回调函数接收响应体、执行请求、检查错误码、清理句柄至少 8 行起步而且很容易忘记释放内存。所以实际项目中我都会基于 libcurl 封装一个轻量级 HttpClient 类。封装的要点统一响应体结构包含 HTTP 状态码、响应 Headers、响应 Body、请求耗时。管理连接句柄每个请求可以复用连接HTTP Keep-Alive减少握手开销。集中处理超时和重试默认超时 5 秒遇到网络抖动自动重试 2 次。支持证书校验开关测试环境经常是自签名 HTTPS 证书需要能一键跳过证书验证。一个典型的 GET 请求在 libcurl 原生 API 下写出来是这样的CURL* curl curl_easy_init(); if (!curl) { // 处理初始化失败 } curl_easy_setopt(curl, CURLOPT_URL, https://api.example.com/orders); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, responseBuffer); CURLcode res curl_easy_perform(curl); if (res ! CURLE_OK) { // 处理请求失败 } curl_easy_cleanup(curl);封装之后HttpResponse resp httpClient.get(https://api.example.com/orders); if (resp.statusCode 200) { // 直接解析 resp.body }看起来只是少了几行代码但在测试用例里这种封装的收益是指数级的。你想想一个测试类里动不动就十几个接口请求如果每个都用原生 API 写光错误处理就会把测试逻辑淹没。2.3 JSON 解析与数据构造nlohmann/json 是效率神器Web 接口返回的数据90% 以上是 JSON 格式。C 场景下常见的 JSON 库有 RapidJSON、nlohmann/json、Boost.PropertyTree。我基本上无脑推荐nlohmann/json原因很简单语法像 Python 的 dict/list易读性非常好头文件单文件不需要编译支持 C11 以上的现代语法写起来很舒服。实际测试中的数据校验经常会写成这样#include nlohmann/json.hpp using json nlohmann::json; json respJson json::parse(resp.body); // 校验订单状态 EXPECT_EQ(respJson[data][status], PAID); // 校验金额字段存在且是数字 ASSERT_TRUE(respJson[data][amount].is_number()); EXPECT_DOUBLE_EQ(respJson[data][amount].getdouble(), 99.90);构造请求体时也直接用 nlohmann 搞定json requestBody; requestBody[user_id] 10086; requestBody[items] { { sku, A1001 }, { count, 2 } }; std::string bodyStr requestBody.dump();很多刚转 C 测试的人会觉得用 Python 写惯字典了C 里用 JSON 太痛苦其实用 nlohmann 以后基本是零负担。2.4 构建体系与依赖管理CMake vcpkg 的配合C 测试项目最难的一环其实是构建配置。很多人在这一步就放弃了——头文件找不到、链接失败、库版本冲突每个坑都能消耗一整天。建议直接用CMake做构建依赖用vcpkg管理。vcpkg 是微软出品的跨平台包管理器安装 libcurl、nlohmann-json、gtest 就三个命令的事vcpkg install curl nlohmann-json gtestCMakeLists.txt 里核心配置大概是cmake_minimum_required(VERSION 3.14) project(web_auto_test) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(CURL REQUIRED) find_package(nlohmann_json REQUIRED) find_package(GTest REQUIRED) add_executable(web_auto_tests tests/api_tests.cpp src/http_client.cpp src/test_helpers.cpp ) target_link_libraries(web_auto_tests PRIVATE CURL::libcurl nlohmann_json::nlohmann_json GTest::gtest_main GTest::gmock )这里有一个小坑必须提醒Windows 上 vcpkg 安装的 libcurl 默认可能不带上 HTTPS 支持需要安装 curl 时额外指定特性vcpkg install curl[ssl]否则你在测试 HTTPS 接口时回调 CRUL E_SSL_ENABLED半天找不到原因。3. 核心函数的封装思路把常用的操作变成顺手工具3.1 统一封装的 HttpClient 类把 libcurl 的复杂性藏起来我在实际项目中开发的 HttpClient 类核心函数只有 6 个但足够覆盖绝大多数 Web 测试场景get(url, headers)GET 请求post(url, body, headers)POST 请求往往带 JSON bodyput(url, body, headers)PUT 请求del(url, headers)DELETE 请求uploadFile(url, filePath)文件上传测试downloadFile(url, savePath)文件下载测试统一的响应体结构定义struct HttpResponse { long statusCode 0; std::string body; std::mapstd::string, std::string headers; double elapsedSeconds 0.0; bool ok() const { return statusCode 200 statusCode 300; } };为什么要管这么多字段因为接口测试中经常需要校验状态码是不是 201创建成功或 204无内容删除成功响应头里的 Content-Type 是不是 application/json响应耗时是不是超过性能基线。这个响应结构体在断言阶段用处极大建议不要偷懒省略。3.2 断言函数封装不只为对错还要给清晰的失败信息GoogleTest 自带断言已经很方便但在 Web 测试场景里我习惯再包一层业务断言函数目的有两个统一错误输出格式和减少重复的判断逻辑。比如校验 HTTP 状态码void assertStatus(const HttpResponse resp, long expectedCode) { EXPECT_EQ(resp.statusCode, expectedCode) 请求 URL: resp.url | 期望状态码: expectedCode | 实际状态码: resp.statusCode | 响应体: resp.body.substr(0, 500); }实际效果就是测试挂的时候日志直接告诉你哪个请求挂了、期望什么、返回了什么、响应体前 500 字是什么。比只输出一个Expected: 200 Actual: 500有用太多。还有 JSON 中的字段级断言也是我工作里最常用的一种封装void assertJsonField(const json j, const std::string path, const json expected) { // 支持用 data.user.name 这样的点路径去读取嵌套字段 json cur j; std::stringstream ss(path); std::string token; while (std::getline(ss, token, .)) { if (!cur.contains(token)) { FAIL() JSON 中不存在字段: path; return; } cur cur[token]; } EXPECT_EQ(cur, expected) JSON 字段: path; }封装之后测试里写断言就非常清爽assertJsonField(respJson, data.user.name, 张三); assertJsonField(respJson, data.order.total, 299.0);这段代码中点路径的设计参考了 Python 的 deep-get 风格可以让一个断言通吃多层嵌套结构实战价值很高——接口返回的 JSON 往往就是三层起跳每次手写嵌套判断容易出错。3.3 测试数据准备函数构造 URL、时间戳、随机字符串、签名接口测试绕不开测试数据的准备特别是唯一性数据。比如你每次跑用例去创建一个新订单订单号就不能用一个固定值否则第二次跑必然冲突。我常用的工具函数std::string generateTimestampId(const std::string prefix) { auto now std::chrono::system_clock::now(); auto timeT std::chrono::system_clock::to_time_t(now); std::tm tmBuf; #if defined(_WIN32) localtime_s(tmBuf, timeT); #else localtime_r(timeT, tmBuf); #endif char buf[64]; std::strftime(buf, sizeof(buf), %Y%m%d%H%M%S, tmBuf); return prefix _ buf; } std::string generateRandomString(size_t length) { static const char chars[] abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789; std::string result; result.reserve(length); std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution dist(0, sizeof(chars) - 2); for (size_t i 0; i length; i) { result chars[dist(gen)]; } return result; }测试里使用std::string orderId generateTimestampId(order); std::string requestToken generateRandomString(32);注意 C 里的localtime不是线程安全的上面代码特意用了localtime_s/localtime_r这就是在多线程压测环境里踩过坑之后的经验。3.4 会话与鉴权处理Cookie 和 Token 的维护方式Web 接口测试里鉴权是最烦人的一环。常见两种模式Session-Cookie 模式先调用登录接口拿到 Set-Cookie后续请求携带 CookieToken 模式登录后拿到 access_token后续请求在 Header 里带Authorization: Bearer token。我在 HttpClient 类里加了一个会话管理机制简单但够用class HttpClient { public: void setCookie(const std::string cookie) { cookie_ cookie; } void setBearerToken(const std::string token) { token_ Bearer token; } private: std::string cookie_; std::string token_; };发送请求时统一附加鉴权信息void HttpClient::prepareHeaders(CURL* curl, const std::mapstd::string, std::string headers) { struct curl_slist* headerList nullptr; for (auto [key, value] : headers) { std::string line key : value; headerList curl_slist_append(headerList, line.c_str()); } if (!cookie_.empty()) { std::string cookieLine Cookie: cookie_; headerList curl_slist_append(headerList, cookieLine.c_str()); } if (!token_.empty()) { std::string authLine Authorization: token_; headerList curl_slist_append(headerList, authLine.c_str()); } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerList); // 注意headerList 需要在整个请求生命周期内保持有效 }一个容易踩的坑curl_slist的释放时机。如果你在函数结束时立刻释放headerListlibcurl 在真正执行请求时可能已经读不到这些头了。正确做法是在curl_easy_perform完成后再curl_slist_free_all或者把headerList作为成员变量延迟释放。我自己是在内部的请求函数末尾统一释放别图省事在 prepare 函数内释放。4. 场景化实战三张场景指南覆盖大部分真实测试需求4.1 场景一接口回归测试的核心流程与完整代码说一个最常见的场景对订单服务的接口做回归测试。需求如下创建一个新订单根据订单 ID 查询订单详情对订单执行支付操作查订单状态确认变成 PAID清理测试订单走取消接口。整个测试用例写成 GoogleTestclass OrderApiTest : public ::testing::Test { protected: void SetUp() override { // 每个用例开始前登录获取 token HttpResponse loginResp client.post(https://api.test.com/login, json{{username, tester}, {password, test123}}.dump()); auto loginJson json::parse(loginResp.body); client.setBearerToken(loginJson[data][token].getstd::string()); } HttpClient client{https://api.test.com, 5.0}; }; TEST_F(OrderApiTest, FullOrderLifecycle) { // 1. 创建订单 json createBody; createBody[sku] generateRandomString(8); createBody[quantity] 2; createBody[price] 99.9; HttpResponse createResp client.post(/orders, createBody.dump()); ASSERT_EQ(createResp.statusCode, 201) createResp.body; auto respJson json::parse(createResp.body); std::string orderId respJson[data][order_id].getstd::string(); ASSERT_FALSE(orderId.empty()); // 2. 查询详情 HttpResponse queryResp client.get(/orders/ orderId); ASSERT_EQ(queryResp.statusCode, 200); auto queryJson json::parse(queryResp.body); EXPECT_EQ(queryJson[data][status], CREATED); // 3. 支付 json payBody; payBody[method] balance; payBody[amount] 199.8; HttpResponse payResp client.post(/orders/ orderId /pay, payBody.dump()); ASSERT_EQ(payResp.statusCode, 200); // 4. 查询状态 HttpResponse verifyResp client.get(/orders/ orderId); auto verifyJson json::parse(verifyResp.body); EXPECT_EQ(verifyJson[data][status], PAID); // 5. 清理 HttpResponse cancelResp client.post(/orders/ orderId /cancel, {}); EXPECT_EQ(cancelResp.statusCode, 200); }这个用例跑完整个创建-查询-支付-确认-清理闭环的覆盖就达成了。关键点在于每个用例都通过 SetUp 预先登录避免测试互相影响测试数据用随机字符串和完整生命周期管理不会在测试环境留下垃圾数据每步断言失败都有上下文信息响应体前 500 字排查问题不需要去翻服务端日志。4.2 场景二并发与性能验证C 的主场秀如果 Python 是心智负担低那 C 在这块的价值就是性能上限高。接口性能验证经常需要同时发出几百上千个请求比如验证商品详情的 99 分位响应时间是否超过 800ms。用 C 写并发测试可以不用引入额外的并发库直接用标准库的std::thread和std::async#include future #include vector void performanceTest() { HttpClient client{https://api.test.com, 10.0}; constexpr int CONCURRENCY 20; constexpr int REQUESTS_PER_THREAD 50; std::atomicint totalRequests{0}; std::atomicint failedRequests{0}; std::vectordouble allElapsed; auto worker []() { for (int i 0; i REQUESTS_PER_THREAD; i) { auto start std::chrono::steady_clock::now(); HttpResponse resp client.get(/products/sku123); auto end std::chrono::steady_clock::now(); double elapsed std::chrono::durationdouble, std::milli(end - start).count(); allElapsed.push_back(elapsed); totalRequests; if (resp.statusCode ! 200) { failedRequests; } } }; std::vectorstd::futurevoid futures; for (int i 0; i CONCURRENCY; i) { futures.push_back(std::async(std::launch::async, worker)); } for (auto f : futures) { f.get(); } // 统计结果 std::sort(allElapsed.begin(), allElapsed.end()); double p50 allElapsed[allElapsed.size() * 50 / 100]; double p95 allElapsed[allElapsed.size() * 95 / 100]; double p99 allElapsed[allElapsed.size() * 99 / 100]; std::cout 总请求数: totalRequests \n 失败数: failedRequests \n P50: p50 ms\n P95: p95 ms\n P99: p99 ms\n; }这里有两个细节值得关注std::async默认可能不会启动新线程需要显式传std::launch::asyncallElapsed在多线程下需要加锁或者改成本地 vector 再合并。上面代码里为了简洁直接 push 了实际项目建议用 mutex 保护否则有数据竞争。我当时跑这个压测用 Python 写同样的逻辑20 并发 1000 请求大约耗时 40 秒含请求序列化、线程切换开销C 版本大概 6 秒。差别主要在语言运行时开销上这也是你在汇报自动化框架性能时最能拿得出手的数据。4.3 场景三接入 CI 流水线让测试自动跑起来自动化测试不接入 CI 等于半个摆设。这块我以 Jenkins 为例。GoogleTest 本身生成的测试报告格式为 XMLJenkins 可以通过插件识别并展示历史趋势。CMake 中需要开启 XML 输出./web_auto_tests --gtest_outputxml:test_reports/在 Jenkins pipeline 中核心步骤大致是pipeline { agent any stages { stage(Build) { steps { cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake cmake --build build --config Release } } stage(Test) { steps { ctest --test-dir build --output-on-failure } } } post { always { junit build/test_reports/*.xml } } }需要注意的点环境变量传递测试环境的 URL、账号密码不要硬编码在源码里。用环境变量或配置文件注入CI 里可以方便切换 Dev/Test/Staging 环境。网络访问如果 CI 节点不能访问测试环境所有用例会全挂。CI 的代理配置和防火墙规则是排查重点。测试稳定性接口测试天然受网络、缓存、第三方依赖影响。在 CI 里建议给关键用例加重跑一次机制比如失败后隔 30 秒重试一次过滤掉偶发的超时问题。这里要特别强调一下环境变量管理。之前有个同事把测试环境的数据库连接字符串硬编码进测试代码后来换环境跑的时候不小心把一个内部 IP 地址泄漏给了外部供应商社死现场。从那以后所有敏感信息账号、密码、Token、IP、端口一概走外部配置。5. 常见问题与排查实录这些都是真实踩过的坑5.1 链接阶段报错CURL 和 OpenSSL 版本不匹配这是 C Web 测试入坑最先遇到的编译问题报错信息一般长这样undefined reference to curl_easy_init排查三步走确认 CMake 里find_package(CURL)是否成功确认target_link_libraries里有没有CURL::libcurl确认 vcpkg 的 triplet 是 x64-windows 还是 x86-windows架构不一致会导致 link 失败。如果用的是 vcpkg 且装的是 curl[ssl]还需要确保 OpenSSL 库也被正确链接。可以在 CMake 里显式加上find_package(OpenSSL REQUIRED) target_link_libraries(web_auto_tests PRIVATE OpenSSL::SSL OpenSSL::Crypto )Windows 环境下还有个常见坑Debug 和 Release 的库必须匹配运行时库/MT 和 /MD。如果你 Debug 构建用 /MDd 的运行时但链接的 curl 是 /MT 的 Release 库那 lunk 阶段会出现各种奇怪的 error C2065。建议把整个项目统一用 /MDRelease和 /MDdDebug不要混。5.2 自签名 HTTPS 证书导致的握手失败测试环境几乎必用自签名证书libcurl 默认会严格校验证书链。报错信息CURLE_SSL_CACERT (60) - peers certificate issuer has been marked as not trusted by the user解决方案有两层临时性方案在测试代码里跳过证书校验curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);这是开发调试阶段最省事的方式但如果你在HttpClient构造函数里通过参数控制这个开关而不是写死是最好的做法HttpClient client{https://api.test.com, 5.0, /*skipCertVerify*/true};生产环境跑测试时把这个参数设为 false保持完整的证书链校验。另一个更规范的方案把测试环境的根证书导出成 PEM并在请求时指定 CA 证书curl_easy_setopt(curl, CURLOPT_CAINFO, /path/to/test-ca.pem); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);这样做的好处是测试是严格校验的能提前发现证书过期、域名不匹配的问题毕竟线上出这类问题可比测试环境严重多了。5.3 响应体中文乱码或编码问题接口返回的 JSON 里有中文时如果服务端没管 header 里的 charset或者代码里没按 UTF-8 解析就会出现乱码。排查思路确认服务端是否返回 Content-Typeapplication/json; charsetutf-8。确认客户端如何读取libcurl 的写回调把字节流写入 string 时不要做任何编码转换原样保存。nlohmann/json 解析时默认按 UTF-8 处理一般不会有问题。Windows 控制台看乱码不代表数据真的乱Windows 控制台默认是 GBK 编码你先写日志到文件再用 UTF-8 编辑器检查别在控制台直接吊日志。实际项目中我会把测试响应的 body 尽可能写成 UTF-8 落盘文件后面用脚本比对或手工检查都方便std::ofstream logFile(response.json, std::ios::binary); logFile resp.body; logFile.close();5.4 接口请求出现偶发超时如何区分是网络问题还是服务问题偶发超时在 Web 自动化测试里最讨厌——单个用例重跑就过了但又不能每次都人工重跑。我的做法是在 HttpClIent 里内置超时重试 耗时记录测试报告里把超时请求单独标记出来。设置 libcurl 超时的关键参数curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 5000L); // 总超时 5 秒 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, 2000L); // 连接超时 2 秒对于重试不是遇到任何错误都重试只重试这么几类CURLE_OPERATION_TIMEDOUT28——真正超时CURLE_COULDNT_CONNECT7——连接失败可能是网络抖动CURLE_GOT_NOTHING52——服务端返回空响应可能是连接被重置。但如果是 4xx/5xx 状态码服务端逻辑错误不要重试。重试反而掩盖了真实问题测试日志也会变得没法看。下面是一个实际排查中的对照思路现象可能原因排查重点某个接口 100% 超时服务端口未监听 / 防火墙拦截先手动 curl 验证一下高并发时大量超时服务线程池打满 / 数据库连接池耗尽看服务端日志与监控指标偶发一次超时重试恢复网络抖动 / 服务 GC 卡顿这种属于偶发因素重试策略兜底全部请求都超时测试机本身网络故障 / 代理配置错误检查测试机网络再谈服务端5.5 JSON 解析异常和断言失败时日志信息怎么组织才不费劲测试挂掉最痛苦的不是挂而是挂了你不知道要看哪。合理组织 failed 日志能省一半时间。我的经验是每个失败断言必须带以下三类上下文请求上下文URL、方法、请求头脱敏后、请求体摘要响应上下文状态码、响应体前 500 字断言上下文期望值和实际值的完整对比。基于这种设计我把断言封装成统一的辅助函数void logFailureContext(const std::string assertName, const HttpResponse resp, const json* responseJson nullptr) { std::cerr [FAILED] assertName \n URL: resp.url \n 状态码: resp.statusCode \n 响应体: resp.body.substr(0, 500) \n; if (responseJson) { std::cerr 响应 JSON 摘要: responseJson-dump().substr(0, 500) \n; } }日志信息虽然长但对排查问题非常友好。团队里新同学跑挂一个用例只需要把日志贴给后端同事对方基本不用问就能定位问题。6. 写在最后的一些体会C Web 自动化测试在整个自动化测试生态里算是小众方向但它的价值恰恰体现在别人搞不定的事上。拿我自己来说最初写这个框架的时候团队里没人看好——觉得 C 测试效率低、上手难、不值得。但实际跑起来以后性能优势、对内存和连接的控制力、以及和现有 C 服务端代码的无缝集成让我觉得这条路走得值。最后分享一个小技巧测试用例不要写得太复杂。很多 C 开发转测试后第一反应是把被测服务端代码的实现细节都搬进测试用例里结果测试代码比被测代码还难维护。好的自动化测试应该像黑盒用户一样操作接口、校验结果而不是复刻服务端逻辑。把核心 HttpClient 和断言函数封装好后面的用例就是堆卡片简单直接才是可持续的。如果这个框架推到你团队里我建议先从纯后端接口的回归用例跑起来跑通之后再考虑扩展性能测试和集成测试。一步一步来C 做 Web 自动化测试是可以走得很远的。