拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C++ Web自动化测试实战:HTTP接口、WebDriver协议与嵌入式设备验证

C++ Web自动化测试实战:HTTP接口、WebDriver协议与嵌入式设备验证

说实话,我第一次被领导要求“用C++写Web自动化测试”的时候,内心是拒绝的。圈子里聊Web自动化测试,默认就是Python+pytest+Selenium,或者Java那套接口自动化框架,C++这个选项几乎没人提。直到后来接手一个纯C++的边缘网关项目,测试同学为了回归Web接口,硬生生维护了两套数据模型,线上接口改个字段,两边都要同步改,一个漏改就是事故。那之后我才下定决心,直接在C++工程里搭一套自动化测试。几年下来,这套用C++做的HTTP接口验证、Web页面驱动、甚至嵌入式Web设备巡检的方案,已经在我们好几个业务线上稳定跑着了。

今天这篇博文不打算绕弯子,直接把常用函数、场景化应用指南、以及我踩过的坑完整拆出来。内容会覆盖HTTP接口测试、WebDriver协议底层调用、ESP32这类嵌入式内嵌Web网页的自动化验证,还有CI集成的工程化细节。不管你是刚入门的C++学习者想找练手方向,还是已经被测试方案困住的工程开发者,应该都能从中找到能直接抄作业的部分。

1. 为什么用C++做Web自动化测试:不只是“反主流”

1.1 什么场景下你才需要它

首先要说明,C++做Web自动化测试不是要替代pytest或者Selenium,而是它的适用场景非常特殊。我整理了一下,真正适合让C++出场的,通常跑不掉下面三种情况。

第一种,被测对象本身就是C++写的服务。比如我们做的是边缘网关固件,核心协议、业务逻辑全在C++层,外部暴露的是HTTP/REST接口。这种情况下,测试同学用Python就得重新维护一套接口封装、一套数据模型,服务端C++结构体一改,Python那侧马上断层。与其这样,不如让测试代码直接复用产品工程里的库,同一个CMake构建系统,同一套编译产物,字段类型天然对齐,接口变更在编译期就能暴露一大部分问题。

第二种,性能敏感的高并发回归。Python写脚本虽然快,但一到高并发场景就容易受GIL限制,进程线程模型也没那么轻。C++配合线程池,几十个线程同时打HTTP请求,内存和CPU开销都比同等Python方案低一个量级。我们曾经做过一个接口压测回归,同样的200并发,C++客户端能跑到接近打满服务端,Python脚本反而先因为GIL变成了瓶颈。

第三种,嵌入式Web设备,比如ESP32内嵌的Web管理页面。很多IoT设备出厂后,板子上根本没有Python运行时,但C++交叉编译出来的静态测试工具往固件里一塞就能跑。即使不在设备上跑,在宿主机上通过WiFi直接访问设备IP做自动化,C++写出来的测试程序也比Python脚本更少依赖,部署到CI执行节点几乎没有环境障碍。

当然,我也得泼一盆冷水。如果是纯粹的前端UI探索性测试,或者产品原型阶段需要快速堆一堆临时脚本,那就别拿C++硬刚。C++的优势是稳定、可控、贴近被测系统,代价是编译调试周期长、动态性差。适合它的场景,是那些已经有稳定C++技术栈、或者对性能和运行时环境有硬约束的团队。

1.2 C++ Web自动化测试的技术栈全貌

决定用C++之后,你需要先建立一张自己的技术地图。我按测试类型整理了一张选型表,下面的实操内容基本都围绕这张表展开。

测试类型常用组件说明
HTTP/REST接口测试cpp-httplib、libcurlcpp-httplib头文件库,API简单,适合快速落地;libcurl底层,控制力最强,需要自己处理回调
JSON解析与断言nlohmann/json现代C++最舒服的JSON库,没有之一,AT运算、迭代、序列化都够用
Web UI自动化直接调WebDriver协议利用ChromeDriver提供的REST接口,C++只写HTTP客户端即可控制浏览器
WebSocket测试Boost.Beast websocket做实时功能验收,需要处理握手、帧收发和二进制流
测试框架与断言GoogleTest、Catch2GTest宏丰富、JUnit报告成熟;Catch2更轻,单头文件就能跑
构建与依赖管理CMake、vcpkg/FetchContent让C++测试工程和产品工程共用一套构建体系,依赖不再靠手拖头文件

这张表里,HTTP客户端、JSON处理、测试框架是日常最高频的三件套,先把它们练熟,基本就能覆盖绝大多数接口自动化测试需求。WebDriver协议、WebSocket这些,属于遇到具体场景再深入研究的部分,后面第3章会分开讲。

2. 常用函数全解析:接口测试三板斧

接口自动化测试说白了就三件事:发HTTP请求、解析返回值、做断言断言比较。把这三件事对应的常用函数吃透,你的测试代码地基就稳了。这一章我按使用频率从高到低逐个说明。

2.1 HTTP请求:cpp-httplib 的Get/Post/Delete

cpp-httplib是一个头文件为主的轻量HTTP库,接口设计非常现代,对从没写过C++网络代码的人也很友好。我用它做接口测试的频率最高,原因很简单:不需要理解底层Socket、不需要写回调,一个Client对象直接调方法就能拿到完整响应。

常用的核心方法就这几个:

  • cli.Get(path, headers),发GET请求
  • cli.Post(path, body, content_type),发POST请求,body传JSON字符串,content_type传"application/json"即可
  • cli.Put(path, body, content_type)、cli.Delete(path),对应HTTP的PUT和DELETE
  • res->status,HTTP状态码
  • res->body,响应体文本
  • res.error(),请求层面的错误枚举,配合httplib::to_string()能输出可读信息

一个典型的GET接口测试长这样:

#include <httplib.h> #include <nlohmann/json.hpp> #include <gtest/gtest.h> using json = nlohmann::json; TEST(ApiTest, GetUserInfo) { httplib::Client cli("https://api.example.com"); cli.set_connection_timeout(5, 0); // 5秒连接超时 cli.set_read_timeout(10, 0); // 10秒读超时 auto res = cli.Get("/users/42", {{"Accept", "application/json"}}); ASSERT_TRUE(res) << "网络错误: " << httplib::to_string(res.error()); ASSERT_EQ(res->status, 200); json body = json::parse(res->body); EXPECT_EQ(body.at("id").get<int>(), 42); EXPECT_TRUE(body.contains("name")); }

这里有两个细节特别容易踩坑。第一个是超时设置,set_connection_timeout管的是建立TCP连接的时间,set_read_timeout管的是拿到响应体前允许的最长等待。测试环境里服务偶发慢,超时设太短会出现“半路扇”的假失败,设太长又会拖慢整条用例集,我一般连接5秒、读取10秒起步,针对慢接口再单独放宽。

第二个是断言前一定要用ASSERT_TRUE(res)判断请求本身是否成功。很多人先写ASSERT_EQ(res->status, 200),一旦网络失败,程序直接解引用空指针崩溃,连错误信息都来不及打,调试效率极低。这是新手最常犯的错。

2.2 底层兜底:libcurl 的核心函数与回调

cpp-httplib虽然好用,但有些场景它不够灵活。比如要精确控制证书校验、要跟踪重定向次数、要往请求里塞自定义Header但不想构造httplib::Headers那么直白,或者要复用一套成熟的连接池逻辑。这时候我通常会绕到libcurl。

libcurl是C语言库,函数的套路比较古老,核心函数必须记清楚:

  • curl_global_init(CURL_GLOBAL_ALL),全局初始化,整个程序生命周期里调用一次即可
  • curl_easy_init(),创建句柄
  • curl_easy_setopt(curl, CURLOPT_xxx, ...),设置URL、超时、回调等
  • curl_easy_perform(curl),同步执行请求
  • curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code),拿HTTP状态码
  • curl_easy_cleanup(curl),释放句柄

最需要注意的是响应体怎么收集。libcurl默认把数据丢给stdout,如果你想拿到字符串,必须提供一个写回调。我一般这样写:

size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* output) { output->append(static_cast<char*>(contents), size * nmemb); return size * nmemb; } std::string http_get(const std::string& url) { CURL* curl = curl_easy_init(); std::string response; curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); curl_easy_perform(curl); curl_easy_cleanup(curl); return response; }

原生libcurl用起来确实啰嗦,所以我建议不管在哪个项目里,都把它包一层RAII类,构造时初始化、析构时清理,别裸奔CURL*。裸指针一旦中间有异常抛出,内存泄漏几乎是必然的。

2.3 JSON处理:nlohmann/json 的高频操作与陷阱

接口测试里JSON就是主战场。nlohmann/json这个库用起来非常顺手,但有几个高频函数和对应的坑必须提前讲清楚。

常用操作有这些:

  • json::parse(str),把字符串解析成JSON对象,解析失败会抛json::parse_error异常
  • body.at("key"),取指定key的值,key不存在会抛出json::out_of_range异常
  • body.contains("key"),判断key是否存在
  • body.dump(),把JSON对象序列化成字符串
  • body.is_object()、body.is_array(),类型判断方法,用来做结构校验

使用上最容易翻车的是运营商方括号body["key"]和at()的选择。很多人图省事用body["key"],但它的行为是:如果key不存在,mud中会主动插入一个null值。这在测试代码里非常危险,等于把一个“请求返回JSON里缺字段”的缺陷,掩盖成了“字段值为null”的误判。我统一要求项目里只准用at()和contains()组合,宁可抛异常,也要让缺字段的问题第一时间爆出来。

再补一个我常用的结构校验写法:

if (body.contains("data") && body.at("data").is_object()) { const auto& data = body.at("data"); EXPECT_TRUE(data.contains("list")); } else { FAIL() << "响应缺少data对象: " << body.dump(); }

2.4 断言与测试框架:GoogleTest常用宏

最后一块拼图是断言。GTest的宏语法简单,基本看一遍就能会。我日常用最多的是这些:

  • EXPECT_EQ(actual, expected)、ASSERT_EQ,判等,ASSERT_系列失败会立刻终止当前用例
  • EXPECT_TRUE(cond)、EXPECT_FALSE(cond),布尔判断
  • EXPECT_NE(a, b),判不等
  • EXPECT_THROW(statement, exception_type),验证某段代码抛指定异常,这个在测异常路径时特别好用
  • FAIL() << "自定义信息",直接失败并输出信息

TEST(ApiTest, GetUserInfo)这个宏定义了一个测试用例。如果用例之间需要共享初始化逻辑,就用TEST_F加fixture,把公共的Client初始化和Token获取放到SetUp()里,避免每个用例重复一遍。

很多人在断言里只写EXPECT_EQ(body.at("code").get<int>(), 200),失败后只看到“期望200实际500”,完全不知道是哪段逻辑的问题。我建议所有关键断言后面都加一段流输出,比如<< "接口响应体: " << body.dump(2),失败时能直接把完整响应打出来,排查效率完全是两个量级。

3. 场景化实战:从接口到浏览器再到嵌入式设备

学会了基础函数,还得看它们怎么组合起来解决实际问题。我挑了四个最典型的场景展开,每个场景都来自真实项目的复盘。

3.1 RESTful接口回归:登录、Token与数据驱动

绝大多数Web系统的接口自动化,第一步都是登录拿凭证。以常见的Token认证为例,测试流程是这样:先用一个测试账号POST登录接口,拿到JSON里的Token,后续所有业务接口在Header里带上Token,再校验响应。

代码骨架长这样:

class ApiFixture : public ::testing::Test { protected: void SetUp() override { cli = std::make_unique<httplib::Client>("https://api.example.com"); cli->set_connection_timeout(5, 0); cli->set_read_timeout(10, 0); auto res = cli->Post("/api/login", json{{"username", "tester01"}, {"password", "Test@123"}}.dump(), "application/json"); ASSERT_TRUE(res); ASSERT_EQ(res->status, 200); token = json::parse(res->body).at("data").at("token").get<std::string>(); } std::unique_ptr<httplib::Client> cli; std::string token; }; TEST_F(ApiFixture, GetDeviceList) { auto res = cli->Get("/api/devices", {{"Authorization", "Bearer " + token}}); ASSERT_TRUE(res); ASSERT_EQ(res->status, 200); json body = json::parse(res->body); ASSERT_TRUE(body.at("data").is_array()); EXPECT_GT(body.at("data").size(), 0); }

这里有个工程化要点:登录请求的地址和账号不能写死在代码里。我可以从环境变量读取,也可以在CMake里通过编译宏注入。这样测试部署到不同环境(测试环境、预发环境)时,不用重新编译就能切换目标服务。

再进一步就是数据驱动。把用例的输入、期望结果抽到JSON或表格文件里,测试代码循环读取并逐条执行。比如测试订单创建的参数校验,就把“缺少金额”“金额为负数”“商品ID不存在”这些case都放在一张表里,每条数据对应一个子测试。C++没有Python参数化装饰器那么方便,但我用std::vector<TestCase>配合EXPECT_*做循环断言,效果一样,而且失败时能通过SCOPED_TRACE定位到具体是哪条数据出的错。

3.2 Web UI测试:用C++直调WebDriver协议控制浏览器

很多人可能不知道,Selenium之所以能控制浏览器,底层靠的是一套WebDriver协议。这个协议本质上就是HTTP REST接口,ChromeDriver在本地端口(默认9515)监听,测试进程发HTTP请求给它,它再反过来驱动Chrome执行动作。So既然它是HTTP接口,C++直接用我们在第2章学的HTTP客户端就能操作浏览器。

这个过程可以概括成四个请求:

第一步,创建Session。POST/session,body里带浏览器能力参数,响应里会返回sessionId。

httplib::Client driver("http://127.0.0.1", 9515); auto res = driver.Post("/session", R"({"capabilities":{"alwaysMatch":{"browserName":"chrome"}}})", "application/json"); ASSERT_TRUE(res && res->status == 200); std::string sessionId = json::parse(res->body) .at("value").at("sessionId").get<std::string>();

第二步,导航到目标页面。POST/session/{sessionId}/url,body是{"url":"https://example.com"}。

第三步,执行操作或获取信息。比如获取页面标题,GET/session/{sessionId}/title,解析响应里value字段就行。要点击元素的话,得先POST定位元素再POST点击,路径类似/session/{sessionId}/element和/session/{sessionId}/element/{elementId}/click。

第四步,收尾。DELETE/session/{sessionId},把浏览器会话关掉,避免Chrome进程残留。

用这套玩法,我们纯C++项目里也能做最基本的Web UI冒烟测试。比如验证登录页面能正常加载、标题正确、关键元素存在,这些步骤全部通过HTTP请求闭环,不需要在测试机额外装Python环境。

要注意的是ChromeDriver和Chrome的版本必须匹配,否则创建Session会直接报错。这个版本匹配很容易被忽略,CI镜像里升级了Chrome忘了升级ChromeDriver,整套UI用例瞬间全红。建议不管是本地还是CI,都用固定的Chrome版本,并在启动脚本里加一层版本检查。

3.3 嵌入式Web设备自动化:ESP32内嵌网页的验证方式

很多IoT设备,比如ESP32做的智能网关,内部会嵌一个Web管理页面,用户在浏览器里配置WiFi、查看状态、升级固件。这类设备做自动化测试,最大的麻烦是运行环境五花八门,很多板子根本不支持Python。

C++在这里的优势体现得淋漓尽致。我常用的做法是在宿主机上用C++写一套测试程序,通过WiFi或局域网直连设备IP,对着它的HTTP接口发请求。

举个例子,验证设备配置下发:

TestDeviceConfig() { httplib::Client cli("http://192.168.1.100"); cli.set_connection_timeout(3, 0); cli.set_read_timeout(5, 0); auto res = cli.Post("/api/config", json{{"ssid", "MyHome"}, {"password", "passw0rd"}}.dump(), "application/json"); ASSERT_TRUE(res); auto retry_res = cli.Get("/api/config"); ASSERT_TRUE(retry_res); json cfg = json::parse(retry_res->body); EXPECT_EQ(cfg.at("ssid").get<std::string>(), "MyHome"); }

这种嵌入式设备测试有两个和普通Web服务很不一样的地方。第一个是响应延迟波动大,设备芯片性能弱,处理一个JSON请求可能要好几百毫秒,并发能力也弱。所以测试程序对单个设备的请求并发度要控制在很低水平,超时要放宽,不能套用云端服务的3秒超时标准。第二个是设备网络不稳定,WiFi偶发性丢包,请求失败不能直接判定产品Bug,要设计重试机制,把“设备没响应”和“设备响应错误”区分开。我在用例里会保留请求次数和重试日志,最终报告中能清楚看到哪些失败是网络抖动导致的。

如果要把设备接进CI,最稳的做法是硬件在环(HIL):一台专门的测试工位,放着一台真实设备,CI流水线在宿主机上编译C++测试程序,跑完把结果传到服务器。虽然不如纯软件测试轻量,但对嵌入式Web设备来说,这已经是最接近真实用户体验的自动化方案了。

3.4 WebSocket实时功能测试:C++也能直播测

WebSocket在实时视频、消息推送、协作编辑这些Web场景里用得越来越多。常规的HTTP接口测试覆盖不到实时连接那部分,于是我也用C++补上了这块。

Boost.Beast里有WebSocket客户端实现,代码上手需要适应一下Asio的模型,但基本套路很清晰:先解析URL、建立TCP连接、再做WebSocket握手,之后就可以互相收发文本帧了。

伪代码流程大概是这样:

// 1. 解析 ws://host:port/path // 2. tcp::iostream 连接 host:port // 3. beast::websocket::stream 包装连接 // 4. ws.handshake(host, path) // 5. ws.write(文本帧或二进制帧) // 6. beast::flat_buffer buffer; ws.read(buffer) // 7. 从 buffer 取出数据,断言字段

我踩过最深的坑是握手的Host字段必须和服务端要求的完全一致,尤其是端口。服务端是Spring Boot集成WebSocket的场景,对Origin和Host校验很严格,写错一个就返回403握手失败。排查的时候直接看服务端日志,比在客户端侧瞎猜效率高。

另外,WebSocket测试里超时策略很重要。实时通道不像HTTP那样请求-响应一一对应,服务端可能主动推消息过来,客户端也可能长时间收不到数据。测试脚本必须能等待异步消息,我一般用Asio的steady_timer做轮询式超时,在5秒内没等到预期消息就直接判失败,同时打印缓冲区里最近收到的几条消息,方便定位是服务端没推,还是推了但内容不对。

4. 工程化落地:CI、环境搭建与问题排查

函数和场景都有了,最后一步是让这套东西在团队里真正跑起来。没有CI、没有依赖管理、没有排错手段,测试代码再漂亮也活不过三个版本。

4.1 从零搭建测试工程:VSCode + CMake + vcpkg

我建议测试工程不要手动拖头文件,那会让依赖版本完全失控。用CMake做构建,依赖交给vcpkg或者FetchContent,两条路都行。vcpkg适合正式项目,版本锁定清晰;FetchContent适合快速起步,直接在CMake脚本里拉Git Tag。

这是一份可以直接参考的CMakeLists.txt:

cmake_minimum_required(VERSION 3.20) project(web_auto_test CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(CURL REQUIRED) find_package(OpenSSL REQUIRED) find_package(GTest REQUIRED) include(FetchContent) FetchContent_Declare(httplib URL https://github.com/yhirose/cpp-httplib/archive/refs/tags/v0.15.3.tar.gz) FetchContent_MakeAvailable(httplib) FetchContent_Declare(json URL https://github.com/nlohmann/json/archive/refs/tags/v3.11.3.tar.gz) FetchContent_MakeAvailable(json) add_executable(test_runner test_main.cpp api_test.cpp) target_compile_definitions(test_runner PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT) target_link_libraries(test_runner PRIVATE httplib::httplib nlohmann_json::nlohmann_json GTest::gtest_main CURL::libcurl OpenSSL::SSL)

如果你正在用VSCode开发,配置C/C++环境这步不复杂:装好C/C++扩展,CMake插件会自动读取上面的CMakeLists并生成构建任务。记得在.vscode/settings.json里把cmake.configureArgs加上-DCMAKE_TOOLCHAIN_FILE=<vcpkg根目录>/scripts/buildsystems/vcpkg.cmake(如果走vcpkg),不然依赖会找不到。Windows上如果要把测试产物拷到别的机器跑,注意目标机器要装匹配的VC++运行库,否则双击就是经典的0xc000007b报错。

4.2 深坑排查:常见问题与速查表

我整理了一份自己在实战中反复遇到的排查速查表,几乎覆盖了C++ Web自动化测试的绝大部分“第一次就跪”的问题。

现象原因解决办法
编译找不到httplib.hinclude路径没配好,或FetchContent未拉取成功检查CMake是否有target_link_libraries(httplib::httplib),VSCode重新加载CMake缓存
链接报错找不到curl未find_package(CURL)或未链接库加find_package(CURL REQUIRED)并链接CURL::libcurl
HTTPS请求报SSL证书错误测试环境常用自签名证书,证书链不信任测试环境可临时enable_server_certificate_verification(false),正式环境应配置CA路径
json::parse抛异常服务端返回非JSON文本,比如返回了HTML错误页解析前打印res->body前几百字符;用ASSERT_NO_THROW包裹解析
WebDriver连接报session not createdChromeDriver版本与Chrome版本不匹配锁定版本号,检查localhost:9515是否被占用
接口返回中文乱码响应头Content-Type缺少charset=utf-8服务端补全响应头;客户端不要盲目做编码转换,按UTF-8字节处理
请求偶发Timeout测试环境网络不稳,或设备处理慢适当调大set_read_timeout,增加重试机制,重试间隔加随机抖动

这里面最值得单独说一句的是SSL证书问题。很多团队第一次跑HTTPS接口测试就被自签名证书卡住,然后一刀切把证书验证关掉,一直带到生产环境。我的经验是测试环境关掉可以,但要在代码里留一个显眼的开关,并且带上注释“仅限测试环境”,防止谁脑子一热把这段搬到生产压测脚本里。

4.3 C++方案和pytest/Selenium怎么选

最后聊聊选型。我不否认Python的pytest和Selenium是Web自动化测试的事实标准,它们生态成熟、脚本短、上手快,该用就用。但C++这套方案也有明确的一席之地,结合我的经验,两者并不冲突。

对比维度C++(cpp-httplib + GTest)Python(pytest + Selenium)
上手成本需要C++和CMake基础,编译期较长脚本即写即跑,入门门槛低
运行性能高,高并发场景能打满服务端受GIL限制,高并发需要多进程绕路
与C++被测系统集成天然无缝,可复用产品库需要跨语言绑定或维护双套模型
UI探索测试可以做,但动态调试弱生态成熟,元素定位、截图报告开箱即用
CI集成编译产物单一,部署简单需要保证Python环境依赖一致
典型场景接口回归、性能压测、嵌入式设备验证前端UI变化多的探索性测试、快速原型验证

我的判断标准很简单:被测系统本身是C++技术栈,或者对性能和环境依赖有硬约束,那就用C++;如果是普通Web应用的UI回归,尤其是页面结构经常调整的前端项目,那就让pytest和Selenium上。工具是为人服务的,不是用来证明谁的方案更酷的。

有一点对面试也特别有用:当你把WebDriver协议底层是REST这个点讲清楚,再补充一段用C++直调ChromeDriver的代码思路,面试官通常会眼前一亮,因为这证明你不只是会用某个框架,而是理解了自动化测试的本质。这套东西在自动化测试面试题里,绝对是个加分项。

写在最后的实操体会

真要说起来,“该不该用C++做Web自动化测试”这个问题本身没有标准答案。我自己在实际项目里,最后还是把C++用在接口回归、性能脚本和嵌入式设备验证上,真正偏页面UI的探索性测试,比如频繁变动的前端交互逻辑,还是交给了Python那套方案。这不是能力不够,而是每个工具都有自己的边界。

如果你准备在自己的项目里试水,我给一个最小可行的起步路径:先用cpp-httplib加GoogleTest搭一个能跑通GET接口的测试工程,跑进CI,然后把登录态的获取逻辑加进fixture,再逐步覆盖你负责模块的核心接口。整个起步过程不用追求大而全,但一定要把断言写规范、把失败信息打清楚。测试代码写多了你就会发现,真正难的往往不是用什么语言,而是你有没有把一个场景拆到位、把每个断言背后的业务逻辑吃透。

返回列表