首页
/ Black 贡献者上手指南:开发环境搭建、测试体系与风格变更规范

Black 贡献者上手指南:开发环境搭建、测试体系与风格变更规范

2026-09-05 22:45:59作者:段琳惟

本文基于 Black 仓库官方贡献文档 docs/contributing/the_basics.md 展开,系统讲解如何从零搭建 Black 的开发环境、提交代码前必须执行的 lint 与测试命令、基于 tests/data/cases 的三段式格式化测试文件如何编写,以及 --print-full-tree/--print-tree-diff 调试开关、CHANGES.md 变更日志要求和风格变更的稳定性政策。读完并动手实践后,你将能够独立为 Black 提交通过 CI 检查的补丁。

一、环境准备:从克隆仓库到可运行的开发环境

官方建议优先使用最新版本的 Python 进行开发,操作系统不限。当前仓库 pyproject.toml 声明 requires-python = ">=3.10",且项目元数据覆盖 CPython 3.10 到 3.15,所以用 3.10+ 的任何发行版都可以起步。

第一步是克隆 Black 仓库并进入目录:

$ git clone https://gitcode.com/GitHub_Trending/bl/black
$ cd black

然后创建一个虚拟环境(任选你熟悉的方式),安装开发依赖,并挂上 git pre-commit 钩子:

$ python3 -m venv .venv
$ source .venv/bin/activate # linux / macOS 激活方式
$ .venv\Scripts\activate   # Windows 激活方式

(.venv)$ pip install --group dev
(.venv)$ pip install -e ".[d]"
(.venv)$ pre-commit install

这几条命令各自的作用,可以从仓库的 pyproject.toml 中得到印证:

  • pip install --group dev 安装的是 PEP 735 依赖组([dependency-groups] 中的 dev 组)。从源码结构看,dev 组实际是 { include-group = "cov-tests" }, { include-group = "tox" }, "pre-commit" 的组合,其中 cov-tests 又聚合了 coveragetests 两组并额外加上 pytest-cov>=4.1.0tests 组提供 pytest>=7pytest-xdist>=3.0.2。也就是说这一条命令一次性装齐了 pytest、覆盖率与并行测试所需的工具链。
  • pip install -e ".[d]" 以可编辑模式安装 Black 本体及其 d 可选依赖(在 pyproject.toml 中定义为 aiohttp>=3.10,用于 blackd HTTP 服务,对应 src/blackd/ 模块)。pip install --group docs 则用于后面文档构建测试,安装 sphinx==9.1.0furomyst-parser 等文档工具链。
  • pre-commit install 注册钩子后,每次 git commit 都会自动执行仓库内置的 .pre-commit-config.yaml。该配置包含 isort、flake8(含 flake8-bugbear 等插件)、mypy、prettier(格式化 Markdown/YAML/JSON)以及 pre-commit-hooks 的 end-of-file-fixertrailing-whitespace 等钩子。值得注意的是其中两个 local 钩子check-pre-commit-rev-in-examplecheck-version-in-the-basics-example(入口分别是 scripts/check_pre_commit_rev_in_example.pyscripts/check_version_in_basics_example.py),它们专门校验文档示例中的版本号/rev 是否与仓库一致——也就是说本文对应的 the_basics.md 文件本身就处于这些自动化检查的覆盖之下。

二、提交 PR 前必须执行的四条命令

在从 Black 仓库根目录提交 pull request 之前,需要运行以下命令完成 lint 与测试:

(.venv)$ pre-commit run -a # Linting
(.venv)$ tox -e py # Unit tests
(.venv)$ tox -e fuzz # Optional Fuzz testing
(.venv)$ tox -e run_self # Format Black itself

结合 tox.ini 可以看到每条命令背后实际发生的事:

  • pre-commit run -a:对全部文件执行上节提到的所有钩子,等价于把「commit 时才会跑的检查」手动完整跑一遍。
  • tox -e pyenvlist{,ci-}py{310,311,312,313,314,315,py3},fuzz,run_self,generate_schemapy 是其中不带版本号的默认环境。该环境的命令序列是:先 pip install -e .[d]coverage erase,再运行 pytest tests --run-optional no_jupyter --numprocesses auto --cov(不装 jupyter 依赖的测试);随后 pip install -e .[jupyter] 再跑 pytest tests --run-optional jupyter -m jupyter --cov --cov-append(jupyter 相关测试),最后 coverage report[testenv]recreate = True 的注释解释了原因:避免第二次运行时 no_jupyter 测试在已安装 jupyter 依赖的环境下被错误执行。
  • tox -e fuzz:使用 hypothesis + hypothesmith 做模糊测试,实际执行 coverage run scripts/fuzz.py(入口脚本见 scripts/fuzz.py)。这一步是可选的。
  • tox -e run_self:Black 用最新源码格式化它自己(black --check {toxinidir}),保证「格式化器的输出对自己稳定」这一底线。

更多测试调用示例

日常开发中常用的补充调用方式:

(.venv)$ tox --parallel=auto # Run all the above in parallel
(.venv)$ tox -e py314 # Run tests on a specific python version
(.venv)$ pytest -k <test name> # Run an individual test
(.venv)$ tox -e py -- --no-cov # Pass arguments to pytest
  • tox -e py314 对应 envlist 中的 py314 环境,可在指定 Python 版本上单独验证行为(对涉及 --minimum-version 的版本相关用例尤其有用);
  • tox -e py -- --no-cov 这类 -- 之后的参数会透传给 testenv 命令里的 {posargs},最终传给 pytest;
  • 直接 pytest -k <test name> 按名称过滤单个测试,是最快的迭代方式。

三、测试体系:tests/data/cases 中的三段式测试文件

Black 要求「风格的所有方面都应有测试覆盖」。测试通常创建为 tests/data/cases 目录下的文件,每个文件最多由三部分组成:

  1. # flags: 开头的行,后跟一组命令行选项。例如 # flags: --preview --skip-magic-trailing-comma 表示该用例在开启 preview 模式、关闭 magic trailing comma 的条件下运行。可接受的选项大体是 Black 自身命令行选项的一个子集,其中 --minimum-version= 有特殊用途:当测试「只有新版本 Python 才能解析的语法特性」时使用,它确保不会在旧版本上尝试做 AST 校验,同时验证 Black 在特性被使用时能正确自动探测 Python 版本。省略该行则使用默认选项。
  2. 一段作为格式化输入的 Python 代码块
  3. # output,其后的内容是 Black 对上面代码块运行后应产生的输出。若省略该行,则测试断言 Black 应保持输入代码原样不变

仓库中可以找到真实示例:tests/data/cases/backslash_before_indent.py 首行为 # flags: --minimum-version=3.10tests/data/cases/comment_type_hint.py 首行为 # flags: --no-preview-line-length-1,都是典型的 flags 行用例。

flags 行的解析实现

「可接受的选项集合」在 tests/util.pyget_flags_parser() 函数中定义(约第 235 行起)。从源码结构看,该解析器用 argparse 注册了 --target-version--line-length--skip-string-normalization--pyi--ipynb--skip-magic-trailing-comma--preview--unstable--fast--minimum-version--line-ranges--no-preview-line-length-1 等参数。其中 --minimum-version 的帮助文本解释了运行机制:设置后该测试用例会被运行两次——一次不带 --target-version,另一次将 --target-version 精确设为指定版本,以此保证版本自动探测逻辑正确。这解释了官方文档中「同时测试自动探测」一句的实现细节。

四、失败用例调试:--print-full-tree 与 --print-tree-diff

Black 有两个作用于 tests/data/ 中「输入/输出分离式」测试文件的 pytest 命令行选项,既可以通过 tox 透传,也可以直接传给 pytest。

--print-full-tree

测试失败时,打印处理完输入之后的完整具体语法树(CST,"actual"),以及解析输出代码后得到的树("expected")。注意:测试可能因输出不同而失败,但两棵 CST 完全相同。文档特别指出,这个行为曾经是默认值,但现在默认是 False

(.venv)$ tox -e py -- --print-full-tree

--print-tree-diff

测试失败时,打印上述两棵树的 diff。这是默认开启的行为;想关闭可传 --print-tree-diff=False

(.venv)$ tox -e py -- --print-tree-diff=False

这两个选项的实现在 tests/conftest.py 中:pytest_addoption() 注册两个选项,模块级默认值 PRINT_FULL_TREE = FalsePRINT_TREE_DIFF = True 与文档描述完全一致,pytest_configure() 再把选项值写回这两个全局变量供断言逻辑使用。配套的单元测试在 tests/test_black.py 中,test_assertFormatEqual_print_full_tree 验证失败输出里出现 Expected tree:Actual tree:test_assertFormatEqual_print_tree_diff 验证 diff 输出里出现 Tree Diff: 与新增的 COMMA 节点——两者均标记 incompatible_with_mypyc(mypyc 编译构建下跳过)。

排查思路建议:先看默认的 tree diff 定位是「哪类节点/叶子」处理分歧;若 diff 看不出问题(例如两棵树相同但序列化输出不同),再开 --print-full-tree 对照完整结构。

五、CHANGES.md 变更日志要求

Black 的 CI 会检查每个 PR 是否在 CHANGES.md 中有对应条目。如果认为你的 PR 不需要变更日志,可在 PR 评论中说明,由维护者添加 ci: skip news 标签让 CI 通过。否则,请确保在合适的标题下方添加一行如下格式的条目:

- `Black` is now more awesome (#X)

注意 X 应是你的 PR 编号,而不是 issue 编号。原文档建议用一个名为 "Next PR Number" 的外部统计工具来估算下一个 PR 号(此处不附外链);这种方式虽不完美,但能显著降低发版工作量——发版者无需再逐条回溯每个发布应该往 CHANGES.md 里补什么内容。

六、风格变更规范与稳定性政策

在提交任何改动前,先熟悉 Black 的稳定性政策(Stability Policy)。据此,大多数风格变更必须加到 --preview 风格中,例外只有两类:修复崩溃,以及不会影响「已经被格式化过文件」的改动。

具体的文档与日志配套规则:

  • 若改动影响对外宣传的代码风格,请同步修改 Black 代码风格文档(即 docs/the_black_code_style/current_style.md)以反映该变化;修复格式化中非预期 bug 的补丁则不需要单独提及。
  • 若改动是通过 --preview 标志实现的,请把该变化写入 docs/the_black_code_style/future_style.md(Future Style 文档),并把变更日志条目写在专门的 "Preview style" 标题下。

从稳定性政策原文可以看到其含义:同一日历年内,用某版本 Black 格式化过的代码,再用同一年其他版本(相同选项)格式化时应保持不变(因此项目可以安全使用 black ~= 26.0 这类兼容约束);新年首个版本可能包含尽量少的格式化变更,以纳入新语言语法的改进;而 --preview--unstable 标志则豁免于该政策。这也解释了为什么 tests/data/cases 中大量 preview_ 前缀用例(如 preview_hug_comparator.py)带有 --preview flags 行。

七、文档构建测试

如果你修改了文档,可以在本地验证其仍可构建:

(.venv)$ pip install --group docs
(.venv)$ pip install -e ".[d]"
(.venv)$ sphinx-build -a -b html -W docs/ docs/_build/

其中 -a 强制全部重建,-b html 输出 HTML,-W 把警告提升为错误——这意味着任何失效的交叉引用、拼错的锚点都会让构建直接失败,与 CI 的严格程度对齐。文档源码位于 docs/(含 docs/conf.pydocs/Makefile),输出目录 docs/_build/ 为本地产物。

顺带说明本文开头提到的「相对链接转换」问题在 Sphinx 项目中的对应物:Black 文档使用 myst-parserpyproject.toml docs 组中的 myst-parser==5.1.0),正文中的 (labels/stability-policy)= 这类显式锚点定义(见 docs/the_black_code_style/index.md)正是各文档互相引用稳定性政策小节所依赖的机制。

八、工程卫生习惯

文档最后强调了两条卫生习惯,值得固化为肌肉记忆:

  • 修 bug 时加测试:先写测试并运行确认它失败,然后修复 bug,再运行测试确认它真的被修复(红-绿循环);
  • 加新特性时加测试:事实上任何时候都应加测试;如果要加大型特性,请先开 issue 讨论后再动手实现。

以上流程——环境搭建(依赖组 + pre-commit 钩子)、四条提交前命令(pre-commit/tox 单元测试/模糊测试/自我格式化)、三段式 cases 测试文件与 get_flags_parser 支持的 flags、tree diff 调试开关、CHANGES.md 编号规范、--preview 风格变更与稳定性政策、sphinx -W 文档构建——共同构成了 Black 仓库当前贡献流程的完整闭环,全部命令均可在 docs/contributing/the_basics.md 及本文引用的源码文件中直接对照验证。

登录后查看全文
热门项目推荐
相关项目推荐