测试 Jupyter Notebook

Testing a Jupyter Notebook

我正在尝试想出一种方法来测试许多 Jupyter 笔记本。当在 Github 分支中实施新笔记本并提交拉取请求时,测试应该 运行。测试并没有那么复杂,它们主要只是测试笔记本 运行 是否端到端且没有任何错误,也许还有一些断言。然而:

我愿意使用任何测试库,例如 'pytest' 或 unittest,尽管 pytest 是首选。

我查看了一些用于测试笔记本的库,例如 nbmake, treon, and testbook,但无法使它们正常工作。我还尝试将笔记本转换为 python 文件,但魔法单元被转换为 get_ipython().run_cell_magic(...) 调用,这成为一个问题,因为 pytest 使用 python 而不是 ipython,而 get_ipython() 仅适用于 ipython。

所以,我想知道在考虑所有这些情况下测试 jupyter 笔记本的好方法是什么。感谢任何帮助。

我已经使用过的一种直接方法是使用 nbconvert.

执行整个笔记本

由于 --execute 选项告诉 nbconvert 在转换前执行笔记本,因此引发异常的笔记本 failed.ipynb 将导致失败 运行。

jupyter nbconvert --to notebook --execute failed.ipynb
# ...
# Exception: FAILED
echo $?
# 1

另一个正确的笔记本 passed.ipynb 将导致成功导出。

jupyter nbconvert --to notebook --execute passed.ipynb
# [NbConvertApp] Converting notebook passed.ipynb to notebook
# [NbConvertApp] Writing 1172 bytes to passed.nbconvert.ipynb
echo $?
# 0

蛋糕上的樱桃,你也可以这样做through the API然后用 Pytest 把它包起来!

import nbformat
import pytest
from nbconvert.preprocessors import ExecutePreprocessor

@pytest.mark.parametrize("notebook", ["passed.ipynb", "failed.ipynb"])
def test_notebook_exec(notebook):
  with open(notebook) as f:
      nb = nbformat.read(f, as_version=4)
      ep = ExecutePreprocessor(timeout=600, kernel_name='python3')
      try:
        assert ep.preprocess(nb) is not None, f"Got empty notebook for {notebook}"
      except Exception:
          assert False, f"Failed executing {notebook}"

运行 测试给出。

pytest test_nbconv.py
# FAILED test_nbconv.py::test_notebook_exec[failed.ipynb] - AssertionError: Failed executing failed.ipynb
# PASSED test_nbconv.py::test_notebook_exec[passed.ipynb]

备注

This doesn’t convert a notebook to a different format per se, instead it allows the running of nbconvert preprocessors on a notebook, and/or conversion to other notebook formats.

  • python 代码示例只是一个草稿,可以在很大程度上进行改进。

这是我自己使用 testbook 的解决方案。假设这是我有一个名为 my_notebook.ipynb 的笔记本,其中包含以下内容:

诀窍是在我调用 bigquery.Client 之前注入一个单元格并模拟它:

from testbook import testbook

@testbook('./my_notebook.ipynb')
def test_get_details(tb):
    tb.inject(
        """
        import mock
        mock_client = mock.MagicMock()
        mock_df = pd.DataFrame()
        mock_df['week'] = range(10)
        mock_df['count'] = 5
        p1 = mock.patch.object(bigquery, 'Client', return_value=mock_client)
        mock_client.query().result().to_dataframe.return_value = mock_df
        p1.start()
        """,
        before=2,
        run=False
    )
    tb.execute()
    dataframe = tb.get('dataframe')
    assert dataframe.shape == (10, 2)

    x = tb.get('x')
    assert x == 7