Marimo项目中使用pytest进行单元测试指南
前言
在数据科学和Python开发中,单元测试是保证代码质量的重要手段。Marimo作为一个交互式笔记本环境,提供了与pytest测试框架的无缝集成,让开发者能够在笔记本环境中直接编写和运行测试用例。本文将详细介绍如何在Marimo项目中使用pytest进行单元测试。
Marimo中的测试机制
Marimo笔记本内置了测试发现和执行功能,当项目中安装了pytest依赖时,Marimo会自动识别并执行符合特定命名规则的测试代码:
- 测试函数:名称以
test_
开头的函数 - 测试类:名称以
Test
开头的类
Marimo会跳过包含非测试代码(如辅助函数、常量、变量、导入等)的单元格,建议将这类代码放在单独的单元格中。
示例代码
@app.cell
def __():
import pytest
def inc(x):
return x + 1
return inc, pytest
@app.cell
def __(inc, pytest):
class TestBlock:
@staticmethod
def test_fails():
assert inc(3) == 5, "This test fails"
@staticmethod
def test_sanity():
assert inc(3) == 4, "This test passes"
@pytest.mark.parametrize(("x", "y"), [(3, 4), (4, 5)])
def test_parameterized(x, y):
assert inc(x) == y
return
配置选项
如果需要禁用自动测试功能,可以在配置文件中设置runtime.reactive_test
选项为false
。
命令行测试
Marimo笔记本本质上是Python程序,因此可以直接使用pytest在命令行中运行测试:
pytest test_notebook.py
该命令会执行笔记本中所有符合测试命名规则的单元格,与在笔记本环境中运行测试的行为一致。
命名建议
为测试单元格命名有两种方式:
- 在笔记本文件中为单元格函数命名
- 使用笔记本编辑器中的单元格操作菜单
测试发现
pytest会自动发现以下类型的笔记本:
- 文件名以
test_
开头的笔记本 - 包含自测代码的笔记本(直接运行
pytest my_notebook.py
)
完整示例
以下是一个完整的测试笔记本示例:
# test_notebook.py
import marimo
__generated_with = "0.10.6"
app = marimo.App()
@app.cell
def _():
def inc(x):
return x + 1
return (inc,)
@app.cell
def test_fails(inc):
assert inc(3) == 5, "This test fails"
@app.cell
def test_sanity(inc):
assert inc(3) == 4, "This test passes"
@app.cell
def collection_of_tests(inc, pytest):
@pytest.mark.parametrize(("x", "y"), [(3, 4), (4, 5)])
def test_answer(x, y):
assert inc(x) == y, "These tests should pass."
@app.cell
def imports():
import pytest
return pytest
运行pytest后的输出示例:
============================= test session starts ==============================
platform linux -- Python 3.12.9, pytest-8.3.5, pluggy-1.5.0
rootdir: /notebooks
configfile: pyproject.toml
collected 4 items
test_notebook.py::test_fails FAILED [ 25%]
test_notebook.py::test_sanity PASSED [ 50%]
test_notebook.py::MarimoTestBlock_0::test_parameterized[3-4] PASSED [ 75%]
test_notebook.py::MarimoTestBlock_0::test_parameterized[4-5] PASSED [100%]
=================================== FAILURES ===================================
__________________________________ test_fails __________________________________
# content of test_notebook.py
import marimo
__generated_with = "0.10.6"
app = marimo.App()
@app.cell
def _():
def inc(x):
return x + 1
return (inc,)
@app.cell
def test_fails(inc):
> assert inc(3) == 5, "This test fails"
E AssertionError: This test fails
E assert 4 == 5
E + where 4 = <function inc>(3)
test_notebook.py:17: AssertionError
=========================== short test summary info ============================
FAILED test_notebook.py::test_fails - AssertionError: This test fails
========================= 1 failed, 3 passed in 0.82s ==========================
最佳实践
- 分离测试代码:将测试代码与实现代码分离到不同的单元格中
- 使用参数化测试:利用
@pytest.mark.parametrize
减少重复代码 - 命名规范:遵循pytest的命名约定,使测试更容易被发现
- 集成到CI/CD:将Marimo笔记本测试纳入持续集成流程
总结
Marimo与pytest的深度集成为数据科学项目提供了强大的测试能力。通过在笔记本中直接编写测试用例,开发者可以更高效地进行测试驱动开发(TDD),确保代码质量的同时保持开发效率。无论是交互式开发还是自动化测试,Marimo都提供了完善的解决方案。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考