
marimo doctest用 Python 内置 doctest 测试 notebook 中 docstring 里的代码片段【免费下载链接】marimoA reactive notebook for Python — run reproducible experiments, query with SQL, execute as a script, deploy as an app, and version with git. Stored as pure Python. All in a modern, AI-native editor.项目地址: https://gitcode.com/GitHub_Trending/ma/marimomarimo notebook 以纯 Python 文件形式存储因此 Python 标准库doctest可以直接用于测试 notebook 函数 docstring 中的代码片段。本文基于 doctest 官方指南 与仓库示例 examples/testing/running_doctests.py 展开带你完整实现「在 marimo 中编写、运行并展示 doctest 结果」的流程并结合 marimo/_ast/app.py 的源码说明 marimo 的依赖图如何保证 doctest 单元格的执行顺序与响应式重跑。1. 为什么 marimo 能直接用 doctestdoctest 是 Python 标准库中用于测试文档内代码片段的模块它扫描函数 docstring 中以开头的交互式片段执行后比对输出。marimo 官方文档给出的核心论据是——marimo notebook 本身就是普通的 Python 程序This works because marimo notebooks are just Python programs。这意味着notebook 里定义的函数是真实的函数对象docstring 完整保留doctest 的解析器可以正常识别其中的示例notebook 文件可以直接用编辑器、marimo run打开也可以当作脚本执行doctest 的测试逻辑既可以嵌在单元格里交互式运行也可以纳入命令行/CI 流程。2. 完整示例running_doctests.py仓库中的官方示例 examples/testing/running_doctests.py 是一个最小可运行的 marimo notebook结构如下完整代码见原文件import marimo __generated_with 0.19.7 app marimo.App(widthmedium) app.cell def _(): import marimo as mo return (mo,) app.function def euclid_mcd(a: int, b: int) - int: Return the MCD between positive a, b. euclid_mcd(42, 24) 6 euclid_mcd(24, 42) 6 euclid_mcd(42, 42) 42 assert a 0 assert b 0 if a b: a, b b, a if (a ! b): r a - b return euclid_mcd(b, r) return a app.cell def _(mo): # Include a reference to each function to test euclid_mcd import doctest failures, success doctest.testmod(verboseTrue) mo.md(fSuccess: {success}, Failures: {failures}) return if __name__ __main__: app.run()这段示例包含三个关键部分逐一拆解2.1 用app.function定义被测函数euclid_mcd是欧几里得辗转相减求最大公约数的递归实现docstring 中内嵌了 3 个 doctest 用例交换参数对称性、相等输入。它通过app.function装饰器声明——从源码 marimo/_ast/app.py#L420-L469 可以看到App.function与App.cell共用cell_manager.cell_decorator机制但额外传入top_levelTrue函数会被注册为一个顶层定义单元格函数名进入 notebook 的变量依赖图可被其他单元格像普通变量一样引用。这正是让 doctest 与 marimo 响应式机制兼容的关键。2.2 在 doctest 单元格中引用被测函数第三个单元格开头有一行容易被忽略的代码注释写明# Include a reference to each function to test euclid_mcd在 marimo 中单元格之间的执行顺序由变量引用关系构成的依赖图决定源码中App._maybe_initialize()会对依赖图做拓扑排序见 marimo/_ast/app.py#L617-L619。在 doctest 单元格里「显式引用」euclid_mcd会在图中建立一条边保证被测函数单元格先于 doctest 单元格执行。仓库自带的扩展示例 marimo/_smoke_tests/doctests.py 中对此注释得更直白# Including these make this doctest reactive——这些引用不仅决定顺序还让 doctest 变成响应式的当euclid_mcd所在单元格变化时marimo 会重新执行 doctest 单元格测试结果随之自动更新。2.3 调用doctest.testmod并展示结果failures, success doctest.testmod(verboseTrue) mo.md(fSuccess: {success}, Failures: {failures})doctest.testmod()无参调用时测试当前模块即 notebook 文件本身所在模块能收集到euclid_mcd等所有定义了 doctest 示例的函数verboseTrue会打印每条示例的执行详情便于在 notebook 输出区观察testmod返回(失败数, 通过数)示例用mo.md把汇总渲染为 Markdown 输出在编辑器中直接可见。3. 运行方式与验证示例 notebook 提供了两种运行入口作为 notebook 运行marimo run examples/testing/running_doctests.py或用编辑器打开。执行后输出区显示verboseTrue的逐条执行记录最后一行渲染为Success: 3, Failures: 0。作为脚本运行文件末尾的if __name__ __main__: app.run()使得python examples/testing/running_doctests.py也能按依赖顺序执行全部单元格——这是 marimo run as a script 特性参见 examples/running_as_a_script/的直接受益场景。需要注意doctest.testmod本身不会因失败抛出异常它只返回计数。因此若想把 doctest 纳入 CI 门禁建议在 doctest 单元格之后追加一个断言单元格如assert failures 0使 notebook 以脚本模式运行时在失败情况下报错退出。这是基于testmod语义的合理做法仓库示例本身未内置该断言。4. 对照示例让失败的 doctest 可见仓库在 marimo/_smoke_tests/doctests.py 中提供了一个「含失败用例」的变体用于冒烟验证 doctest 流程它在正确实现euclid_mcd之外故意定义了一个错误实现app.function def bad_multiply_by_2(a: int) - int: Multiply a by 2 and return the result. bad_multiply_by_2(2) 4 bad_multiply_by_2(3) 6 return a 2docstring 声称「乘 2」实现却是「加 2」于是bad_multiply_by_2(3)返回 5 而非期望的 6doctest.testmod(verboseTrue)会在输出中打印该条示例的Expected: 6 / Got: 5失败详情最终汇总为Success: 3, Failures: 1。对照这两个文件可以直观验证 doctest 在 marimo 单元格中既能通过、也能如实报告失败。5. doctest 与 pytestmarimo 的两种测试路径marimo 测试体系在 docs/guides/testing/index.md 中归纳为两条路径doctest 与 pytest 指南 互为补充维度doctestpytest测试内容docstring 中的代码片段单元格里以test_开头的函数/Test类编写位置函数 docstring 内独立单元格如 examples/testing/test_with_pytest.py触发方式在单元格中显式调用doctest.testmod()marimo 在存在 pytest 依赖时自动发现测试单元格命令行直接pytest亦可适用场景轻量用法示例即测试、API 行为快照断言、参数化、fixture 等完整单元测试从源码结构看marimo 对 pytest 路径做了深度集成marimo/_ast/pytest.py 会在模块加载期解析测试单元格、构造MarimoTestBlock_*桩类并注入 notebook 模块使pytest的静态收集能直接发现 notebook 中的测试这也解释了 pytest 指南中 fixture「不能跨单元格引用」的限制来源。而 doctest 路径则完全依托标准库与「notebook 即 Python 程序」这一事实零额外依赖。6. 小结marimo notebook 是纯 Python 文件doctest标准库可直接对其 docstring 片段做测试无需任何 marimo 专属 API官方模式是用app.function定义带 doctest 示例的函数 → 在独立单元格中显式引用被测函数建立依赖边保证执行顺序并支持响应式重跑→ 调用doctest.testmod(verboseTrue)并用mo.md渲染结果可用marimo run或脚本模式运行 examples/testing/running_doctests.py 验证需要 CI 门禁时可自行追加failures 0断言需要更完整的测试能力fixture、参数化、Test 类时转向 docs/guides/testing/pytest.md 描述的 pytest 路径。【免费下载链接】marimoA reactive notebook for Python — run reproducible experiments, query with SQL, execute as a script, deploy as an app, and version with git. Stored as pure Python. All in a modern, AI-native editor.项目地址: https://gitcode.com/GitHub_Trending/ma/marimo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考