资讯动态

pytest接口自动化框架工程化实战:从脚本到持续集成

发布时间:2026/9/8 4:15:44 来源:尧图企业网站定制
做了几年接口测试pytest用得越来越顺手之后我发现很多人的瓶颈根本不在“会不会写用例”而在于“怎么把一团散装的脚本组织成一个能长期维护、能扛住几百条用例、能接进CI流水线的正经项目”。上一篇基础篇讲了fixture、断言、参数化这些积木怎么玩这一篇就聊聊怎么把这些积木搭成一个完整的架子。这篇续集的核心内容围绕四块展开框架分层设计、接口关联与数据传递、数据驱动与请求封装、allure报告与工程化集成。如果你已经能写pytest用例但总觉得项目结构乱、用例之间耦合严重、跑完一头雾水不知道挂了几个是因为环境还是因为代码那这篇就是冲着你来的。1. 整体架构设计从小脚本到大工程的必经之路1.1 为什么框架需要分层很多测试同学写接口自动化最开始的路径都是从postman或者apifox手点开始的。点着点着觉得烦了就把请求直接搬进pytest脚本里一个.py文件里堆了二三十个函数每个函数里requests.get、断言、打印日志一气呵成。这种写法跑起来确实没问题但项目一旦到了几百条用例、需要多人协作的时候问题就全冒出来了。我在实际项目里见过最典型的例子一个test_api.py文件四千多行里面有三个人加班的痕迹接口地址变了要全局搜索替换token获取逻辑散落在十几个函数里跑挂了根本分不清是环境问题还是代码问题。这种代码别说维护三个月三周之后原作者自己看着都头疼。所以框架分层这件事核心目的只有一个让变化的地方足够集中让稳定不变的逻辑沉淀成公共能力。接口地址会变、请求参数会变、断言条件会变这些变化应该被隔离在固定的位置而发请求、读配置、记录日志、生成报告这些通用机制应该各自独立。落到pytest框架里我给团队定的分层思路一般是四层用例层、业务层、核心层、配置层。用例层只负责描述“我要测什么场景”里面不出现requests、不出现url、不出现token业务层负责把“登录”“下单”“退款”这些业务动作封装成可调用的函数或类核心层负责requests会话管理、配置读取、日志、断言封装这类基础设施配置层统一放环境地址、账号密码、开关项这类会变化的数据。这个分层的价值我多说一句它本质上是在给每一次变更划定影响范围。接口加了字段你只需要改用例层或业务层环境从测试切到预发你只需要改配置层底层从requests换成httpx你只需要改核心层。如果哪一次需求变更让你在十几个文件里来回改同一件事大概率就是架构没分好。1.2 目录结构与文件职责规划具体到工程目录我常用的结构长这样api_test_project/ ├── config/ │ ├── __init__.py │ ├── settings.py # 全局配置读取环境变量 │ └── test_config.yaml # 环境、账号、超时等可配置数据 ├── core/ │ ├── __init__.py │ ├── http_client.py # requests.Session封装 │ ├── log_util.py # 日志初始化 │ └── assert_util.py # 断言封装 ├── api/ │ ├── __init__.py │ ├── base_api.py # API基类处理公共逻辑 │ ├── user_api.py # 用户模块接口 │ └── order_api.py # 订单模块接口 ├── testcases/ │ ├── __init__.py │ ├── conftest.py # 全局fixture │ ├── test_user.py # 用户模块用例 │ └── test_order.py # 订单模块用例 ├── data/ │ ├── login_data.yaml │ └── order_data.yaml ├── reports/ │ ├── logs/ # 运行日志 │ └── allure/ # allure原始结果 ├── pytest.ini └── requirements.txt这个结构是我在多个项目里不断调整后的版本。config负责放配置core放与业务无关的基础能力api层放接口调用testcases放用例data放测试数据。每层只依赖比自己更底层的内容不允许用例层直接import core层的封装之外的东西。有个细节值得单独强调api层几乎都做成类而不是散装函数。原因很简单同一组业务接口之间经常共享状态。比如下单接口可能依赖登录态的token、购物车接口也依赖同一套登录态放进一个类里类的属性就可以天然维护这些共享状态比函数传参要优雅得多。1.3 配置管理的设计要点配置管理看起来简单实际上坑不少。我见过直接写死BASE_URL http://192.168.1.100:8080的也见过每个模块各自定义一个config.py的。这两种都会在环境切换或者对接CI时造成麻烦。我的做法是全局只有一个配置入口且允许被环境变量覆盖。# config/settings.py import os from pathlib import Path ROOT_DIR Path(__file__).resolve().parent.parent ENV os.getenv(TEST_ENV, test) ENV_CONFIG_MAP { test: { base_url: http://test.internal.example.com, timeout: 10, }, staging: { base_url: http://staging.internal.example.com, timeout: 15, }, prod: { base_url: http://api.example.com, timeout: 15, }, } current_config ENV_CONFIG_MAP.get(ENV, ENV_CONFIG_MAP[test]) BASE_URL os.getenv(BASE_URL, current_config[base_url]) TIMEOUT int(os.getenv(API_TIMEOUT, current_config[timeout]))TEST_ENV这个环境变量在本地运行时可以在pytest.ini里配好默认值在Jenkins或GitLab CI里则由流水线注入。这样同一套代码本地调试用test环境发布前验证用staging环境完全不需要改任何代码。配置这块还有个小习惯我强烈建议养成敏感信息密码、密钥永远不要直接写进yaml或py文件。至少用环境变量注入更规范的做法是接Vault这类密钥管理服务。测试环境的账号密码泄露同样会造成安全问题别觉得“测试环境无所谓”。2. 接口关联与数据传递让用例之间安全地共享数据2.1 token跨用例复用的正确姿势接口测试里最普遍的关联数据就是token。登录接口拿到token后面的业务接口都带着它发起请求。如果每个用例都去调一次登录接口几百条用例跑下来光登录就占了一大半时间而且登录接口本身也可能因为频率限制或验证码策略变得不稳定。pytest里处理这种“整个会话只需要执行一次、但所有用例都要用到”的场景首选就是session级别的autouse fixture。# testcases/conftest.py import pytest from core.http_client import HttpClient pytest.fixture(scopesession, autouseTrue) def auth_token(): 整个测试会话只登录一次返回可用的token client HttpClient.get_client() resp client.post(/api/v1/auth/login, json{ username: tester, password: ****** }) assert resp.status_code 200, f登录失败: {resp.text} token resp.json()[data][token] yield token这里有一个很关键的工程细节fixture在yield之后的代码会在session结束之后执行。如果在fixture里做了资源创建比如创建了一个测试用户、一条测试订单完全可以在yield之后再写清理逻辑。这个机制我项目里叫“自动回收”非常实用。token拿到之后怎么传给业务接口我见过不少人用全局变量或者写进一个token.txt文件这两种都偏hack。更干净的方式是把token注入到请求会话的公共Header里。2.2 依赖接口响应的场景怎么组织实际测试里更常见的关联是A接口返回的id要作为B接口的入参。比如先创建订单拿到order_id再用order_id去查询订单详情、支付订单。这种用pytest的fixture依赖机制特别顺手。# testcases/test_order_flow.py import pytest pytest.fixture() def created_order(auth_token): 创建一笔测试订单返回订单信息 resp api_order.create_order( tokenauth_token, product_idP1001, quantity2 ) assert resp.status_code 200 return resp.json()[data] def test_get_order_detail(created_order): detail api_order.get_order_detail(created_order[order_id]) assert detail.status_code 200 assert detail.json()[data][status] CREATED def test_pay_success(created_order): pay_resp api_order.pay_order(created_order[order_id]) assert pay_resp.status_code 200 assert pay_resp.json()[data][paid_amount] created_order[total_amount]注意这里created_order是function级别的fixture每个测试函数都会重新走一遍创建订单的流程。模块之间并发跑的时候fixture的数据隔离性远比共享一个全局变量安全。我见过不少团队就是用test_order_flow.py里第一条用例创建的order_id赋值给一个模块变量后面用例直接读这个变量。这在用例按顺序串行执行时能跑通但一旦你加上pytest-xdist跑并发或者单独执行某一条用例pytest test_order_flow.py::test_pay_success就会因为变量不存在而挂掉。所以fixture从设计上就逼你把依赖关系显式声明出来这本身就是一种代码质量的提升。2.3 数据隔离与清理策略数据隔离是接口自动化里最容易翻车的地方。原因很简单你要测一条订单的流程上次跑挂了一条脏数据残留下来这次用例再跑就撞上了。我的经验是把测试数据分成两类来处理。一类是每次执行前需要清理或重建的“临时数据”比如订单、购物车、支付流水另一类是基本不变的“基础数据”比如已创建的产品、配置好的优惠券模板。临时数据建议用“前置清理后置清理”双保险。执行用例前调用删除接口把该用户的历史订单清掉执行完成后再把本次创建的数据删掉。清理失败也要有明确日志避免脏数据堆积。# testcases/conftest.py pytest.fixture() def clean_user_orders(auth_token): 用例执行前清理用户的订单数据 user_id 10086 resp api_user.get_orders(auth_token, user_id) assert resp.status_code 200 for order in resp.json()[data][list]: api_admin.delete_order(auth_token, order[order_id]) yield # 用例结束后的清理逻辑失败仅告警不阻塞 resp api_user.get_orders(auth_token, user_id) for order in resp.json()[data][list]: api_admin.delete_order(auth_token, order[order_id])基础数据不建议在用例里重复创建否则会产生大量垃圾数据。正确做法是接口自动化开始前用一次性的初始化脚本把基础数据准备齐全用例层直接引用固定的数据标识。3. 数据驱动与用例组织把“改代码”变成“改数据”3.1 用yaml承载测试数据的好处接口测试里数据驱动的意义比单元测试更大。因为接口参数组合实在太多了——正常入参、边界值、非法类型、缺字段、超长字符串、特殊字符、组合依赖……如果每一条都写成独立的测试函数代码量会爆炸且肉眼很难看出哪些组合覆盖了、哪些漏了。我的做法是把参数组合放到yaml文件里管理# data/order_data.yaml create_order: - case_id: 正常创建-最小参数 product_id: P1001 quantity: 1 expected_code: 200 expected_msg: success - case_id: 数量为0 product_id: P1001 quantity: 0 expected_code: 400 expected_msg: quantity must be positive - case_id: 数量超上限 product_id: P1001 quantity: 1000 expected_code: 400 expected_msg: quantity exceeds limit - case_id: 商品ID不存在 product_id: NOT_EXIST quantity: 1 expected_code: 404 expected_msg: product not found用例层通过pytest的parametrize加载这些数据# testcases/test_order_create.py import pytest import yaml from pathlib import Path DATA_PATH Path(__file__).parent.parent / data def load_yaml_data(filename, key): with open(DATA_PATH / filename, r, encodingutf-8) as f: data yaml.safe_load(f) return data[key] pytest.mark.parametrize(case_data, load_yaml_data(order_data.yaml, create_order), idslambda d: d[case_id]) def test_create_order(case_data): resp api_order.create_order( product_idcase_data[product_id], quantitycase_data[quantity] ) assert resp.status_code case_data[expected_code] assert case_data[expected_msg] in resp.textids参数值得多说一句。如果不加这个参数pytest展示出来的用例名就是一行字典跑挂了看报告根本分不清是哪一组参数。加上ids之后每条用例都带一个可读的名字比如test_create_order[正常创建-最小参数]一眼就能定位。3.2 参数化与fixture的组合技巧参数化结合fixture时有一个很多新手踩过的坑fixture的入参不能直接拿到parametrize里的参数值需要用request.getfixturevalue或者把参数值传到fixture内。更优雅的做法是让fixture接收request实例pytest.fixture() def order_env(request): env request.param client HttpClient.get_client(env) yield client pytest.mark.parametrize(order_env, [test, staging], indirectTrue) def test_order_in_env(order_env): resp order_env.get(/api/v1/health) assert resp.status_code 200用indirectTrue时parametrize里的参数值会被当作fixture的入参传入让fixture根据不同的值表现不同行为。这样一套用例就能在不同环境间复用。这个技巧在做环境一致性验证时特别有用。3.3 用例标记与筛选策略pytest的mark机制是组织用例的利器尤其是接口多了、执行时间长了以后没有人会想全量回归。我项目里的mark设计一般分两个维度一个是业务模块一个是执行级别。# pytest.ini [pytest] markers smoke: 冒烟用例核心主流程 p1: 高优用例发布前必跑 p2: 中优用例日常回归 order: 订单模块 user: 用户模块 slow: 耗时较长的用例默认不跑有了mark执行选择就变得极其灵活# 只跑冒烟用例 pytest -m smoke # 跑订单模块的P1用例 pytest -m order and p1 # 跳过慢用例 pytest -m not slow # 跑除了登录模块之外的所有用例登录用例可能需要验证码环境 pytest -m not login还有一点容易被忽略pytest.ini里最好配置好addopts把默认参数固定下来。[pytest] addopts -v -s --alluredirreports/allure --clean-alluredir testpaths testcases这样团队成员执行pytest时就自动带上了报告输出和测试路径不需要每个人手动敲一堆参数。团队协作时这种“开箱即用”的默认配置能省下不少沟通成本。4. 请求封装、日志与断言把基础设施打磨顺手4.1 基于requests.Session的核心封装很多pytest接口项目直接用requests.get、requests.post写请求。短平快确实没问题但当你的项目需要统一处理token注入、统一超时、统一日志记录、统一重试时散装的requests调用会让你改到怀疑人生。我的核心封装是requests.Session的扩展# core/http_client.py import time import threading import requests from core.log_util import logger from config.settings import BASE_URL, TIMEOUT class HttpClient: _instance None _lock threading.Lock() def __new__(cls): if cls._instance is None: with cls._lock: if cls._instance is None: cls._instance super().__new__(cls) return cls._instance def __init__(self): if not hasattr(self, _session): self._session requests.Session() self._session.headers.update({ Content-Type: application/json, User-Agent: api-test/1.0, }) def request(self, method, url, **kwargs): final_url url if url.startswith(http) else BASE_URL url kwargs.setdefault(timeout, TIMEOUT) start time.time() try: resp self._session.request(method, final_url, **kwargs) logger.info(f[{method.upper()}] {final_url} - {resp.status_code} | {time.time() - start:.3f}s) return resp except requests.Timeout: logger.error(f[{method.upper()}] {final_url} - TIMEOUT after {TIMEOUT}s) raise except requests.ConnectionError as e: logger.error(f[{method.upper()}] {final_url} - CONNECTION ERROR: {e}) raise def get(self, url, **kwargs): return self.request(GET, url, **kwargs) def post(self, url, **kwargs): return self.request(POST, url, **kwargs) classmethod def set_token(cls, token): cls()._session.headers.update({Authorization: fBearer {token}}) classmethod def get_client(cls): return cls()这里用了单例模式整个测试进程共享同一个Session。好处是连接复用减少TCP握手开销在大量请求的场景下性能提升明显token只需要设置一次后续所有请求自动携带。单例用线程锁保护是因为pytest-xdist跑并发时多进程环境还好但单进程多线程场景下不加锁可能初始化多个实例。这里属于“多发防患于未然”的写法不写其实大部分场景也碰不到写上更稳。4.2 日志体系让接口测试可观测日志这块我吃过亏。早期做接口自动化断言失败时只看得到“expected 200 but got 500”完全不记得当时传了什么参数、请求体是什么、响应完整内容是什么。排查问题基本靠print加猜效率极低。现在的做法是三个层次的日志请求日志、响应体日志、断言日志。请求发出时记录方法、URL、请求体摘要响应返回时记录状态码、耗时、响应体摘要断言失败时把期望值和实际值完整打出来。我推荐用loguru替代logging模块因为它的配置成本几乎为零且开箱即用支持格式化输出、按文件大小轮转# core/log_util.py import sys from pathlib import Path from loguru import logger LOG_DIR Path(__file__).parent.parent / reports / logs LOG_DIR.mkdir(parentsTrue, exist_okTrue) logger.remove() logger.add( sys.stdout, formatgreen{time:YYYY-MM-DD HH:mm:ss.SSS}/green | level{level: 7}/level | {message}, levelINFO, ) logger.add( str(LOG_DIR / api_test_{time:YYYY-MM-DD}.log), rotation00:00, retention14 days, levelDEBUG, encodingutf-8, )文件日志保留14天每天零点轮转。日志文件如果无限增长CI机器上过几个月就会把磁盘塞满到时候处理起来非常被动。4.3 断言封装的取舍pytest原生断言用起来已经很顺了assert resp.status_code 200失败时pytest会给出详细比对。那为什么还要封装断言我封装断言的根本原因是面向业务语义。接口返回的结构经常是{code: 0, msg: success, data: {...}}如果每次断言都写assert resp.json()[code] 0代码里会重复大量低价值的样板代码。# core/assert_util.py from core.log_util import logger def assert_code(resp, expect_code: int, business_code: int None): 校验HTTP状态码可选校验业务code assert resp.status_code expect_code, ( fHTTP状态码不匹配: expect{expect_code}, actual{resp.status_code}, body{resp.text[:500]} ) if business_code is not None: actual_biz_code resp.json().get(code) assert actual_biz_code business_code, ( f业务码不匹配: expect{business_code}, actual{actual_biz_code}, body{resp.text[:500]} ) def assert_field_equals(resp, field: str, expect_value): 校验响应中某个字段的值 data resp.json() actual data.get(data, {}).get(field) assert actual expect_value, ( f字段[{field}]不匹配: expect{expect_value}, actual{actual}, body{resp.text[:500]} )注意断言信息里我每次都会带上resp.text[:500]。这条经验是我踩坑踩出来的断言失败但response内容很完整时光是看日志就能定位问题根本不需要远程到测试环境抓包。断言的失败信息应该自带足够的上下文这是接口测试断言设计和普通单元测试断言设计的一个显著差异。5. 测试报告与持续集成让结果说话让流程自动5.1 allure报告的关键配置pytest的默认文本输出适合本地调试但一旦进入团队协作和CI环节就需要一份像样的测试报告。allure目前是接口测试领域的事实标准和pytest的集成非常顺滑。先说安装。allure本身是Java生态的工具需要先装命令行pytest侧再装allure-pytest两件事不能混为一谈。装完之后在pytest.ini里配置输出目录[pytest] addopts -v -s --alluredirreports/allure --clean-alluredir跑完测试后本地预览报告allure serve reports/allure这里有一个实践要点--clean-alluredir非常关键。如果不加这个参数上一次跑挂的用例结果会残留在allure目录里导致报告里出现“幽灵用例”——明明这次没执行的用例因为历史结果文件还在照样显示在报告中。这个坑我见不少人踩过。allure的报告质量和你在用例里塞了多少信息成正比。我的习惯是在关键用例上加这些装饰器import allure allure.feature(订单模块) allure.story(创建订单) allure.title(校验金额计算的精度) allure.severity(allure.severity_level.CRITICAL) def test_order_amount_precision(): ...feature对应报告里的功能模块story对应具体的用户场景title让用例名更可读severity标注用例级别。调试的时候可以先不加等用例稳定了再择机补全。报告最终是给整个团队看的做得好不好直接影响大家对自动化测试的信心。5.2 接入Jenkins流水线的配置建议接口自动化跑在本地终归是小打小闹真正发挥价值是接进CI/CD流水线提交代码后自动触发、定时回归、质量门禁。我用Jenkins比较多分享几个关键配置点。流水线里最核心的是参数化构建至少包含两个参数TEST_ENV目标环境和TEST_MARK要跑的标记。这样运维同学在Jenkins界面上点点下拉框就能切换环境、选择用例集不用去改代码。pipeline { agent any parameters { choice(name: TEST_ENV, choices: [test, staging], description: 选择测试环境) string(name: TEST_MARK, defaultValue: p1, description: 通过pytest标记筛选用例例如 smoke, p1, order) } environment { PYTHONENCODING UTF-8 } stages { stage(安装依赖) { steps { sh python3 -m pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple } } stage(执行接口测试) { steps { sh TEST_ENV${params.TEST_ENV} pytest -m ${params.TEST_MARK} } } stage(生成allure报告) { steps { script { // 需要服务器上预先安装allure命令行工具 allure generate reports/allure -o reports/allure-html --clean } } } } post { always { allure includeProperties: false, jdk: , report: reports/allure-html, results: [[path: reports/allure]] } failure { // 可以在这里加企业微信/钉钉/邮件通知 } } }这里有个细节执行测试时用环境变量传入TEST_ENV而不是修改配置文件。原因很简单——Jenkins的workspace在每次构建时可能是全新的如果依赖配置文件去改环境文件内容在并发构建时可能互相覆盖。环境变量天然隔离不会串。还有一点要提醒不要把账号密码直接写进Jenkinsfile提交到代码仓库。Jenkins有自带的Credentials管理流水线里引用凭据ID即可。团队协作时仓库权限再严格也不如让敏感信息彻底从仓库里消失来得安心。5.3 质量门禁的设定思路接入CI之后紧接着的问题就是什么情况下算“通过”什么情况下要“卡住”发布。我的建议是不要一刀切“必须100%通过”。接口测试用例多了之后偶尔因为环境波动、第三方依赖不稳定出现零星失败是很常见的。如果失败率在5%以内且都是非核心用例卡住发布对团队来说就是消耗耐心。更具操作性的做法是区分门禁用例和观察用例。门禁用例通常是冒烟用例和P0用例这些挂了必须阻断P2/P3用例挂了只记录进入失败分析队列。实现方式不复杂用--strict-markers和-m标记配合即可CI里跑两遍一遍跑smokep1作为门禁一遍跑p2p3作为趋势参考。另外一个我强烈建议做的对历史结果做趋势存储。allure本身会生成历史趋势图但前提是每次构建的结果目录不能清空。在Jenkins里保持reports/allure-html/history目录的累积allure就能自动画出用例通过率的趋势曲线。这个曲线是评估自动化测试健康度的标尺曲线长期下滑说明自动化在失去价值需要停下来修整而不是继续堆用例。6. 常见问题与排查技巧实录6.1 用例间数据串扰现象单条用例跑总是通过全量执行时偶发失败且失败用例不固定。这是我接手过的项目里排查最耗时的问题之一。原因通常是两条一是有全局变量在用例间悄悄共享比如某条用例把响应里的某个值赋值给了模块级变量二是接口关联的数据靠接口内部默认值兜底前面用例创建的脏数据影响了后面的用例。排查思路先固定随机因子用pytest-randomly插件或者给种子固定随机顺序看失败是否可复现再逐条隔离用例找到触发污染的源头。解决手段就是把共享数据全部收敛到fixture里让pytest管理生命周期而不是靠自定义全局变量。6.2 环境切换后批量失败现象一键从test环境切到staging环境后冒烟用例大量报401或404。原因通常是staging环境缺少测试数据或鉴权方式与test环境不同。这暴露的是配置管理不彻底URL环境变了但用例里硬编码的测试账号、参数值没跟着变。建议给每个环境分配相对独立的测试账号和基础数据初始化脚本在流水线里按环境名拉取对应的配置块。切换环境后不要只看状态码还要确认数据层面是否就绪。6.3 慢接口拖垮整体执行现象全量回归里有一批用例平均耗时5秒以上占了整个执行时长的六成。处理思路跟代码优化一样先量化再优化。我的做法是先输出一份用例耗时排名——pytest内置的--durations20参数可以列出最耗时的20条用例。定位之后按耗时原因分类处理如果是单接口响应慢考虑是否要对该接口做性能评估而不是接口自动化如果是等待业务异步处理用轮询封装替代固定sleep如果是对大量数据做断言校验考虑精简断言字段。pytest --durations20这条命令我一直放在CI日志里每次跑完顺手看一眼时间长了哪些用例变慢了会非常明显。6.4 定位问题时的三分法接口测试失败后第一步永远不是翻代码而是先明确问题层面。我常用一个“三分法”第一分是不是环境问题。目标服务挂了吗依赖的第三方接口超时了吗数据库连接异常了吗这些需要看日志和监控告警。第二分是不是测试数据问题。测试账号被锁定脏数据残留导致前置条件不满足测试环境被别的团队清掉了这些需要查数据库和测试环境状态。第三分才是代码或用例本身的bug。业务代码改了行为接口返回结构变了用例断言写错了实际操作中这个顺序能帮我节省大量时间。曾经有同事查一个接口回归失败查了一下午最后发现是测试环境的Redis被清空了根本不是代码或用例问题。先看环境、再看数据、最后看逻辑这个排查顺序对接口自动化来说永远适用。最后再分享一个小技巧接口测试框架从脚本往工程化的过程中不要试图一步到位。先做到分层清晰、配置收敛、日志可查这三件事能在比较短的时间内完成并且立刻改善可维护性。数据驱动、allure、CI集成可以在这个基础上逐步加每加一样都解决一个明确的痛点。我见过不少团队一开始就照着网上最复杂的架构模板堆功能结果跑都跑不起来反而磨掉了信心。从够用出发、以好用为目标一步步迭代这才是接口自动化框架活下来的方式。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价