写完了三年文档,今天发现「文档和代码不同步」这件事终于有解了——从 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

这件事做完你会发现:文档质量的上限不是人决定的,是机制决定的。把测试做进去,质量自然就守住了。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 47 阅读