gauge-python 实战:用 Markdown 写可读性测试用例的 BDD 框架

简介:gauge-python 是 Gauge 自动化测试框架的 Python 语言运行器插件,面向使用 Python 编写验收测试、行为驱动测试的测试开发与自动化工程师。它让 Gauge 能够识别并执行 Python 编写的 step 实现,解决跨语言测试框架中 Python 支持缺失的问题,适合已掌握 Python 基础、希望引入 Gauge 做可维护测试组织的团队。资源包共 56 个文件,约 70KB,以 34 个 py 源码文件为核心,涵盖解析器、执行器、校验器、注册表、静态加载与重构等模块,另有 6 个 md 文档、3 个 yml 与 3 个 sh 脚本,以及 tmpl、properties、bat、json、ini 等配置与构建文件,结构完整。目前已有 372 人学习下载。通过该资源可了解 Gauge-Python 的插件实现机制、step 注册与执行流程、消息校验与日志处理方式,并借助测试用例与构建脚本掌握从源码构建、运行测试到覆盖率统计的完整链路,为二次开发或排错提供参考。

1. 当测试脚本开始"说人话":gauge-python 到底解决了什么

如果你写过 Selenium 或 Playwright 的 Python 测试,大概率经历过这种场景:一个登录流程写了 80 行代码,产品经理看了一眼说"这我看不懂,但我想改一下断言"。于是你被迫在代码和需求之间来回翻译,最后测试用例变成了只有你自己能维护的黑匣子。gauge-python 就是冲着这个痛点来的——它是 Gauge 测试框架的 Python 语言运行器,让你用 Markdown 写测试场景,用 Python 写底层实现,业务方看得懂前半段,工程师管得住后半段。

Gauge 本身是 ThoughtWorks 开源的 BDD 风格测试工具,核心思路是把"做什么"和"怎么做"拆开:.spec文件用自然语言描述场景步骤,.py文件里实现这些步骤的具体逻辑。gauge-python 就是那个把两边粘起来的运行器,负责解析 spec 文件、匹配步骤实现、驱动 Python 代码执行、收集结果并生成报告。它适合已经有一定 Python 测试基础、但被用例可读性和维护成本折磨的团队,也适合想从 unittest/pytest 迁移到更结构化测试体系的个人开发者。

和 pytest 相比,Gauge 多了一层"规格文件"的抽象,代价是初期配置稍重,收益是测试用例本身变成了可交付的文档。和 Robot Framework 相比,Gauge 的步骤实现更贴近原生 Python,不需要学一套新的关键字语法。选不选它,取决于你的团队里"非技术角色"对测试用例的参与度有多高。

2. 环境搭建与项目骨架:从零到跑通第一条 spec

2.1 安装 Gauge 与 Python 运行器

Gauge 本体是一个独立的命令行工具,Python 运行器是它的插件。两者要分开装,这是第一个容易翻车的地方——很多人只pip install gauge然后发现命令行里根本没有gauge命令。

# 第一步:安装 Gauge 本体(以 macOS 为例,其他平台见官方文档) brew install gauge # 验证 Gauge 本体是否可用 gauge --version # 输出类似:Gauge version: 1.6.x # 第二步:安装 Python 语言运行器插件 gauge install python # 验证插件是否装好 gauge install --list # 应该能看到 python (version: 0.4.x) 之类的条目

这里的关键点是:gauge install python装的是运行器插件,不是 Python 包。插件本身会依赖gauge-python这个 pip 包,但你不要手动去pip install gauge-python然后指望命令行能用——插件的注册机制走的是 Gauge 自己的插件目录,不是 pip 的 entry_points。

提示:如果你在 CI 环境里跑,建议把 Gauge 本体和插件版本都锁死。插件升级偶尔会改步骤匹配的优先级规则,导致原本能跑的 spec 突然报 "step implementation not found"。

2.2 初始化项目结构与 spec 文件编写

Gauge 项目有一套约定俗成的目录结构,不要自己乱建。用gauge init python生成骨架是最稳的做法。

# 创建一个新项目目录 mkdir my-gauge-project && cd my-gauge-project # 用 python 模板初始化 gauge init python # 查看生成的结构 tree -L 2

生成的结构大致如下:

my-gauge-project/ ├── env/ │ └── default/ │ └── default.properties # 环境配置 ├── specs/ │ └── example.spec # 测试规格文件 ├── tests/ │ └── step_impl.py # 步骤实现 └── manifest.json # 项目元信息

specs/下放.spec文件,tests/下放步骤实现。一个最简单的 spec 长这样:

# 用户登录功能 ## 正常登录场景 * 打开登录页面 * 输入用户名 "testuser" 和密码 "pass123" * 点击登录按钮 * 验证页面标题包含 "欢迎"

每个*开头的行就是一个步骤。Gauge 会拿这行文本去tests/目录里找匹配的实现。匹配规则是"参数化步骤"——引号里的内容会被当作参数传给 Python 函数。

2.3 步骤实现与参数传递

步骤实现文件里,用@step装饰器把 Python 函数和 spec 里的步骤文本绑起来。

from getgauge.python import step, before_suite, after_suite @step("打开登录页面") def open_login_page(): # 这里写实际的浏览器操作,比如 driver.get("http://localhost:8000/login") print("导航到登录页") @step("输入用户名 <username> 和密码 <password>") def enter_credentials(username, password): # 参数按顺序注入,类型默认是 str print(f"输入用户名: {username}, 密码: {password}") @step("点击登录按钮") def click_login(): print("点击登录") @step("验证页面标题包含 <expected_title>") def verify_title(expected_title): # 实际断言逻辑 actual_title = "欢迎回来" # 模拟获取 assert expected_title in actual_title, f"期望标题包含 {expected_title},实际是 {actual_title}"

参数传递有两种写法:<param>和"param"。前者是占位符语法,后者是引号语法。区别在于,引号语法会把引号内的内容原样传入,适合包含空格的参数;占位符语法更灵活,但遇到特殊字符时容易解析出错。我一般建议:参数里可能有空格就用引号,纯标识符用占位符。

注意:@step装饰的函数名不重要,重要的是装饰器里的文本必须和 spec 里的步骤文本完全一致(包括标点)。中文标点尤其容易出问题——spec 里写了中文逗号,实现里写了英文逗号,Gauge 不会报错,只会说"找不到步骤实现"。

3. 数据驱动与标签过滤:让同一套步骤跑十组数据

3.1 用表格实现参数化场景

Gauge 最实用的功能之一是表格驱动。你不需要在 Python 里写循环,直接在 spec 里用 Markdown 表格定义多组数据。

## 多用户登录验证 |用户名 |密码 |期望结果 | |---------|--------|---------| |admin |admin123|成功 | |guest |guest123|成功 | |locked |lock123 |账号锁定 | * 使用 <用户名> 和 <密码> 登录 * 验证结果为 <期望结果>

对应的步骤实现:

from getgauge.python import step @step("使用 <username> 和 <password> 登录") def login_with(username, password): # 实际调用登录接口或 UI 操作 result = do_login(username, password) # 假设的登录函数 # 把结果存到场景上下文,供后续步骤使用 from getgauge.python import data_store data_store.scenario["login_result"] = result @step("验证结果为 <expected>") def verify_result(expected): from getgauge.python import data_store actual = data_store.scenario["login_result"] assert actual == expected, f"期望 {expected},实际 {actual}"

表格的每一行会被当作一个独立场景执行,步骤里的<列名>会被替换成对应单元格的值。这里有个细节:表格列名和步骤里的占位符名称必须一致,但和 Python 函数的参数名不需要一致——Gauge 是按位置传参的,不是按名字。

3.2 标签过滤与执行策略

当项目变大,你不可能每次都跑全量。Gauge 支持在 spec 和 scenario 级别打标签,然后用--tags过滤。

# 用户管理 @smoke ## 快速登录检查 * 打开登录页面 * 输入用户名 "admin" 和密码 "admin123" * 验证登录成功 @regression ## 完整用户流程 * 打开登录页面 * 输入用户名 "admin" 和密码 "admin123" * 进入个人中心 * 修改昵称 * 验证昵称已更新

执行时:

# 只跑 smoke 标签的场景 gauge run --tags "smoke" specs/ # 跑 smoke 或 regression gauge run --tags "smoke,regression" specs/ # 排除 regression gauge run --tags "!regression" specs/

标签过滤的坑在于:标签是大小写敏感的,@Smoke和@smoke是两个不同的标签。另外,spec 级别的标签会继承给下面所有 scenario,但 scenario 自己的标签不会反向影响 spec。如果你在 CI 里用标签做分层执行,建议在项目初期就定好标签命名规范,比如@smoke、@regression、@slow、@api,不要临时起意。

3.3 钩子函数与执行生命周期

Gauge 提供了一套钩子,让你在 suite、spec、scenario、step 各个级别插入前置/后置逻辑。最常用的是before_suite和after_suite,用来初始化和销毁浏览器驱动。

from getgauge.python import before_suite, after_suite, before_scenario, after_scenario driver = None @before_suite def init_driver(): global driver # 实际项目里这里初始化 Selenium 或 Playwright driver = "mock_driver" print("Suite 开始,驱动已初始化") @after_suite def cleanup_driver(): global driver driver = None print("Suite 结束,驱动已释放") @before_scenario def reset_state(): # 每个场景前重置登录状态、清空缓存等 print("场景开始,状态已重置") @after_scenario def capture_on_failure(): # 场景失败时截图或记录日志 print("场景结束")

钩子的执行顺序是:before_suite→before_spec→before_scenario→before_step→ 步骤执行 →after_step→after_scenario→after_spec→after_suite。如果你在before_suite里启动了浏览器,在after_suite里一定要关掉,否则 CI 机器上会残留一堆僵尸进程。血泪经验:有一次忘了写after_suite,跑了 200 个场景后内存直接爆了。

4. 避坑与排查:那些让新手卡半天的常见问题

4.1 步骤找不到:Step implementation not found

现象:gauge run specs/报错,提示某个步骤没有对应的实现。

原因:九成情况是文本不匹配。包括但不限于:中英文标点混用、多余空格、参数占位符名称写错、步骤实现文件没有被 Gauge 扫描到。

解决:先检查tests/目录下是否有.py文件,且文件里用了@step装饰器。然后逐字符对比 spec 里的步骤文本和装饰器里的文本。如果用了参数占位符,确认<param>的尖括号是英文半角。最后,gauge run --verbose可以看到 Gauge 扫描了哪些文件、匹配了哪些步骤。

4.2 参数类型全是字符串

现象:spec 里写了数字123,Python 函数收到的却是"123",做数值比较时断言失败。

原因:Gauge 从 spec 解析出来的参数默认都是字符串,不会自动推断类型。

解决:在步骤实现里手动转换,或者在 spec 里用引号明确表示这是字符串。如果参数是数字,建议在 Python 函数里用int()或float()转一下,并加 try-except 处理转换失败的情况。不要指望 Gauge 帮你做类型推断,它不是干这个的。

4.3 表格数据里的空单元格

现象:表格某一行某个单元格留空,执行时报参数数量不匹配。

原因:Gauge 解析表格时,空单元格会被当作空字符串,但如果你在步骤里期望的是"跳过这一行",它不会自动跳过。

解决:要么把空单元格填上占位符(比如N/A),要么在步骤实现里判断参数是否为空字符串,然后决定是否执行。如果确实需要跳过某些行,建议拆成两个表格,而不是在一个表格里留空。

4.4 并行执行时的上下文污染

现象:用gauge run --parallel跑多个 spec,发现场景之间互相干扰,比如一个场景改了全局变量,另一个场景读到了脏数据。

原因:Gauge 的并行是基于 spec 级别的,同一个 spec 内的场景默认串行。但如果你在before_suite里用了全局变量,而并行时每个进程都会执行一次before_suite,全局变量在进程间不共享,反而可能导致状态不一致。

解决:并行执行时,避免用模块级全局变量存状态。用data_store.scenario存场景级数据,用data_store.spec存 spec 级数据。如果必须共享资源(比如数据库连接池),确保它是进程安全的,或者用外部服务来管理状态。

4.5 报告生成失败或路径不对

现象:gauge run跑完了,但reports/html-report目录是空的,或者报告里没有截图。

原因:Gauge 默认生成 HTML 报告,但需要插件支持。如果你没装html-report插件,或者env/default/default.properties里的gauge_reports_dir配置被改了,报告就不会出现在预期位置。

解决:先gauge install html-report,然后检查default.properties里的gauge_reports_dir和overwrite_reports配置。如果要在 CI 里归档报告,建议把gauge_reports_dir设成绝对路径,避免相对路径在不同工作目录下解析不一致。

5. 进阶技巧:用 Gauge 做 API 测试与 CI 集成

5.1 把 Gauge 当 API 测试框架用

Gauge 不绑定 UI 测试,用它做 API 测试反而更轻量。你不需要浏览器驱动,只需要一个 HTTP 客户端库。

import requests from getgauge.python import step, data_store BASE_URL = "http://localhost:8000/api" @step("发送 GET 请求到 <path>") def send_get(path): resp = requests.get(f"{BASE_URL}/{path}", timeout=5) data_store.scenario["response"] = resp @step("验证状态码为 <code>") def verify_status(code): resp = data_store.scenario["response"] assert resp.status_code == int(code), f"期望 {code},实际 {resp.status_code}" @step("验证响应 JSON 中 <key> 的值为 <value>") def verify_json_field(key, value): resp = data_store.scenario["response"] actual = resp.json().get(key) assert str(actual) == value, f"字段 {key} 期望 {value},实际 {actual}"

对应的 spec:

# 用户 API 测试 ## 获取用户列表 * 发送 GET 请求到 "users" * 验证状态码为 "200" * 验证响应 JSON 中 "total" 的值为 "3"

这种写法的好处是:接口测试用例变成了可读的文档,前端和后端都能看懂。而且 Gauge 的报告会按 spec 分组,比 pytest 的纯文本输出更直观。

5.2 CI 集成与执行策略

在 CI 里跑 Gauge,核心是三步:装 Gauge 本体、装 Python 插件、跑gauge run。以 GitHub Actions 为例:

name: Gauge Tests on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install Gauge run: | curl -SsL https://downloads.gauge.org/stable | sh sudo mv gauge /usr/local/bin/ - name: Install Python runner run: gauge install python - name: Install dependencies run: pip install -r requirements.txt - name: Run tests run: gauge run --tags "!slow" specs/ - name: Upload report if: always() uses: actions/upload-artifact@v4 with: name: gauge-report path: reports/

这里有几个实操细节:gauge install python在 CI 里可能会因为网络问题失败,建议加个重试或者用--pin指定版本。另外,--tags "!slow"用来排除慢速用例,保证 CI 反馈速度。报告上传用if: always(),这样即使测试失败也能拿到报告。

5.3 一个容易被忽略的调试技巧

Gauge 的--verbose会输出大量日志,但在排查步骤匹配问题时非常有用。另一个技巧是:在步骤实现里用print输出关键变量,Gauge 会把这些输出捕获到报告里。如果你在 CI 里看不到 print 输出,检查default.properties里的gauge_clear_state_level配置,有时候状态被清了,日志也跟着没了。

从那以后我每次新建 Gauge 项目,都会先跑一遍gauge run --verbose specs/example.spec,确认步骤匹配链路是通的,再开始写业务步骤。这个习惯帮我省掉了至少三次"为什么步骤找不到"的无效排查。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

【信息科学与工程学】【数据科学】数据科学领域 第六篇 算法设计 101 负载均衡/资源分配/并行算法/流算法/在线算法 /Qos算法/计算机图形图像算法

【信息科学与工程学】【数据科学】数据科学领域 第六篇 算法设计 101 负载均衡/资源分配/并行算法/流算法/在线算法 /Qos算法/计算机图形图像算法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 2:09:45 阅读更多 →
霍尔与差分运放电流采集:原理、选型、电路设计与调试避坑指南

霍尔与差分运放电流采集:原理、选型、电路设计与调试避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 2:08:45 阅读更多 →
FOC学习笔记-串口通信

FOC学习笔记-串口通信

实现串口IDLEDMA双缓冲区环形缓冲区 1.CbueMX配置 模式配置 DMA配置 参数配置 2.环形缓冲区 这部分代码参考于netube99/RingBuffer: 基于C语言开发的环形缓冲区 RingBuffer.h #ifndef __RING_BUFFER_H_ #define __RING_BUFFER_H_#include "stdint.h"/*** brief …

2026/10/21 2:08:45 阅读更多 →

最新新闻

思科模拟器6.2:从安装到避坑的完整实战指南

思科模拟器6.2:从安装到避坑的完整实战指南

简介&#xff1a;这是一份思科模拟器6.2安装包&#xff0c;官方名称为Packet Tracer&#xff0c;面向网络技术学习者、思科认证考生以及开展网络实验教学的教师。软件以图形化方式搭建虚拟网络环境&#xff0c;可模拟路由器、交换机、无线接入点、防火墙等设备&#xff0c;支持…

2026/10/21 2:47:00 阅读更多 →
思科模拟器6.2全攻略:从网络仿真到CCNA实验与排错

思科模拟器6.2全攻略:从网络仿真到CCNA实验与排错

简介&#xff1a;网络仿真工具是网络工程师和认证学习者不可或缺的实践平台&#xff0c;它通过软件模拟真实设备的行为&#xff0c;让复杂的协议交互和故障排查变得可视化、可重复。思科模拟器作为经典的网络仿真环境&#xff0c;不仅支持路由、交换、无线等基础实验&#xff0…

2026/10/21 2:47:00 阅读更多 →
iOS地图定位Demo:原生MapKit+CoreLocation从权限到落图

iOS地图定位Demo:原生MapKit+CoreLocation从权限到落图

简介&#xff1a;一个面向iOS开发者的地图定位入门Demo&#xff0c;演示Core Location与MapKit的集成方式&#xff0c;清晰解决定位权限申请、位置获取、地图展示等基础问题&#xff0c;适合初学者到中级开发者作为实践参考。压缩包共23个文件&#xff0c;主要包含Objective-C源…

2026/10/21 2:47:00 阅读更多 →
拆解一个做 PPT 工具:素材自动变大纲一键出片

拆解一个做 PPT 工具:素材自动变大纲一键出片

## 背景这篇记录一次把做 PPT流程工程化的尝试。## 核心能力- 文本素材自动梳理大纲- 大纲支持二次编辑- 多套配色模板可选- 渲染16:9标准PPTX文件## 落地场景学习提升等场景都能直接搬进工作流&#xff0c;输入是散乱的原始材料&#xff0c;输出是可直接交付的成品。## 实现要…

2026/10/21 2:47:00 阅读更多 →
Git核心操作与四区模型详解:从基础命令到协作实战避坑指南

Git核心操作与四区模型详解:从基础命令到协作实战避坑指南

1. 为什么“git 基本操作”值得单独拎出来讲刚入行那会儿&#xff0c;我对 Git 的态度就是“能用就行”——clone、pull、push三板斧走天下&#xff0c;遇到冲突就慌&#xff0c;遇到回退就懵&#xff0c;实在不行就把整个项目删了重新拉一遍。后来参与协作的项目多了&#xff…

2026/10/21 2:47:00 阅读更多 →
Hurys毫米波雷达CAN调试软件安装与通信故障排查指南

Hurys毫米波雷达CAN调试软件安装与通信故障排查指南

简介&#xff1a;本资源是面向汽车电子工程师与ADAS系统开发者的毫米波雷达调试工具集&#xff0c;聚焦Hurys雷达参数配置、CAN通信联调与故障诊断等核心场景&#xff0c;解决雷达实车测试中软硬件协同调试难、驱动兼容性差、数据解析门槛高等实际问题。压缩包共19个文件&#…

2026/10/21 2:46:00 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →