写完了三年文档,今天发现「文档和代码不同步」这件事终于有解了——从 doctest 到 Dredd,我把这套自动化测试的账全算清楚了
你遇到过这种情况吗:代码改了,文档没人动,用户照着文档跑,报错了,去 issue 里问,答曰「文档还没更新」。这个问题太常见了,业内甚至有个专门的名字:文档漂移(Documentation Drift)。
问题出在哪?不是团队不负责任,是文档测试从来都是纯手工活,没有机制让它和代码同步。我跑了三年,今天终于把这件事的系统化解法做完整了。
先从最容易的入手:Python 的 doctest
如果你用 Python,doctest 是零成本起点。它把测试代码直接写进文档注释里。
def factorial(n):
"""Return the factorial of n, an exact integer >= 0.
>>> factorial(0)
1
>>> factorial(5)
120
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n must be >= 0
"""
if n < 0:
raise ValueError("n must be >= 0")
if n == 0:
return 1
return n * factorial(n - 1)
>>> 开头的是交互式 Python 会话,期望的输出紧跟其后。运行 python -m doctest your_module.py,它逐行跑这些片段,输出对不上就报错。
这就是最简单形态的文档测试——文档里嵌着可执行代码,代码一变,文档立刻失效。
上规模?Sphinx 把这件事做进了 CI
Python 社区最早把这件事做大了。 Sphinx 是 Python 官方文档工具,它的 sphinx.ext.doctest 扩展把 doctest 做进了文档构建流水线。
安装 Sphinx 时选上 doctest 扩展,在 conf.py 里加一行:
extensions = [
"sphinx.ext.doctest",
]
然后在文档里直接写可测试的代码块:
.. doctest::
>>> import mymodule
>>> mymodule.add(2, 3)
5
>>> mymodule.add(-1, 1)
0
跑 sphinx-build 时,doctest 扩展会自动执行这些代码块,对不上就构建失败。更重要的是它支持 :hide: 选项隐藏 setup 代码、支持 :skipif: 条件跳过、支持 testsetup 做前置准备——这套东西让文档测试从「一个人手动检查」变成了「CI 自动跑,每次 PR 都过一遍」。
用 Sphinx 做文档的项目,每次合并代码都会触发文档测试——代码改了但文档没同步,PR 直接标红。这才叫把文档质量做进了工程流程。
API 文档不一样:试试 Dredd
Python doctest 解决的是代码示例问题,但 API 文档不一样。API 文档描述的是 HTTP 接口,参数、返回值、错误码,这类东西需要实际发请求才能验证。
Dredd 是专门干这件事的工具。它读 API 文档(支持 OpenAPI/Swagger 和 API Blueprint 格式),然后对你的真实服务发请求,验证响应是否和文档描述一致。
基本工作流是这样的:
第一步:写 API 文档(以 API Blueprint 为例)
FORMAT: 1A
HOST: https://api.example.com/v1
# Users API
## Users [/users]
### List Users [GET]
+ Response 200 (application/json)
+ Attributes (array[User])
第二步:写 Hooks 处理认证等前置逻辑
// hooks.js
"use strict";
var hooks = require("hooks");
hooks.beforeEach(function (transaction) {
// 每个请求前注入 API Key
transaction.fullPath = transaction.fullPath + "?apikey=" + process.env.API_KEY;
});
第三步:接进 CI
# dredd.yml
blueprint: api.apib
endpoint: "https://api.example.com"
hookfiles: ./hooks.js
language: nodejs
# package.json
"scripts": {
"test": "dredd"
}
每次 push 代码,Dredd 会对照文档逐个请求你的 API,响应结构或状态码对不上就失败。API 文档漂移?CI 直接拦住,根本不让它进生产。
这套思路真正值钱的地方
很多人以为这件事的好处是「文档更准」,但跑完我发现它真正改变的是两个东西。
第一个:写文档的人变认真了。 以前写文档,代码示例跑不跑得过心里没底,反正上线没人查。现在知道 CI 会真跑,写的时候就会多打一个 >>> 验证一下。文档质量是副产品,工程约束才是本质。
第二个:代码重构敢动手了。 大项目重构最怕动到一处别人依赖的 API 没同步通知。有了 Dredd,重构完跑一遍 API 测试,文档和实现不一致的地方全暴露出来,比人工 review 靠谱得多。
下一步:从哪开始
如果你现在想动手,记住一个原则:从最痛的文档类型开始,先解决 API 文档或核心函数的代码示例,不要一开始就想着覆盖全部文档。
- Python 项目:从
python -m doctest开始,把核心函数的 docstring 写成可测试的 - 有 API 文档的项目:找一个端点做 Dredd 试点,跑通后再扩
- Sphinx 用户:开
sphinx.ext.doctest扩展,配置进 CI
这件事做完你会发现:文档质量的上限不是人决定的,是机制决定的。把测试做进去,质量自然就守住了。
评论区
登录后可评论。