【问题标题】:Testing a Jupyter Notebook测试 Jupyter 笔记本
【发布时间】:2022-01-11 18:29:21
【问题描述】:

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

  • 某些单元格中的某些调用需要模拟,例如从数据库下载数据的调用。
  • 笔记本中可能有一些运行 pip 命令或其他命令的魔法单元。

我愿意使用任何测试库,例如“pytest”或unittest,但首选pytest

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

所以,我想知道什么是测试 jupyter notebook 的好方法。任何帮助表示赞赏。

【问题讨论】:

    标签: testing jupyter-notebook mocking pytest python-unittest


    【解决方案1】:

    我已经使用的一种直接方法是使用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]
    

    注意事项

    这不会将笔记本本身转换为其他格式,而是允许在笔记本上运行 nbconvert 预处理器,和/或转换为其他笔记本格式。

    • python 代码示例只是一个草稿,它可以大大改进。

    【讨论】:

    • 感谢您的回答。你能解释一下你是如何在这种方法中模拟笔记本中使用的对象(例如 BigQuery 客户端)吗?另外,你如何处理魔法单元(例如“!pip install pandas”)?
    • @shahins 魔法将被执行——您可以使用--debug 标志检查发生了什么。对于模拟部分我不知道。如果要确保所有单元格的接口都可用,则模拟是一个复杂的主题。也许每个笔记本都应该提供一个“模拟”模式——一种在不插入所有东西的情况下执行它的方式。没有这个,试图从外部模拟一切将是一场地狱。
    • 很好,只要您不需要模拟对象,我认为此解决方案效果很好。然而,就我而言,这是一项主要要求。
    【解决方案2】:

    这是我自己使用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
    

    【讨论】:

      猜你喜欢
      • 2017-03-03
      • 2019-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-16
      相关资源
      最近更新 更多