84

我有一个打算重复运行的 Jupyter 笔记本。它里面有函数,代码的结构是这样的:

def construct_url(data):
    ...
    return url

def scrape_url(url):
    ... # fetch url, extract data
    return parsed_data

for i in mylist: 
    url = construct_url(i)
    data = scrape_url(url)
    ... # use the data to do analysis

我想为construct_url和编写测试scrape_url。这样做最明智的方法是什么?

我考虑过的一些方法:

  • 将函数移出实用程序文件,并在一些标准 Python 测试库中为该实用程序文件编写测试。可能是最好的选择,尽管这意味着并非所有代码都在笔记本中可见。
  • 使用测试数据在笔记本本身内写入断言(向笔记本添加噪音)。
  • 使用专门的 Jupyter 测试来测试单元格的内容(不要认为这行得通,因为单元格的内容会发生变化)。
4

10 回答 10

103

Python 标准测试工具,例如doctestunittest,可以直接在笔记本中使用。

文档测试

在文档字符串中具有函数和测试用例的笔记本单元格:

def add(a, b):
    '''
    This is a test:
    >>> add(2, 2)
    5
    '''
    return a + b

运行文档字符串中所有测试用例的笔记本单元(笔记本中的最后一个):

import doctest
doctest.testmod(verbose=True)

输出:

Trying:
    add(2, 2)
Expecting:
    5
**********************************************************************
File "__main__", line 4, in __main__.add
Failed example:
    add(2, 2)
Expected:
    5
Got:
    4
1 items had no tests:
    __main__
**********************************************************************
1 items had failures:
   1 of   1 in __main__.add
1 tests in 2 items.
0 passed and 1 failed.
***Test Failed*** 1 failures.

单元测试

具有以下功能的笔记本单元格:

def add(a, b):
    return a + b

包含测试用例的笔记本单元(笔记本中的最后一个单元)。单元格中的最后一行在执行单元格时运行测试用例:

import unittest

class TestNotebook(unittest.TestCase):
    
    def test_add(self):
        self.assertEqual(add(2, 2), 5)
        

unittest.main(argv=[''], verbosity=2, exit=False)

输出:

test_add (__main__.TestNotebook) ... FAIL

======================================================================
FAIL: test_add (__main__.TestNotebook)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython-input-15-4409ad9ffaea>", line 6, in test_add
    self.assertEqual(add(2, 2), 5)
AssertionError: 4 != 5

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)

调试失败的测试

在调试失败的测试时,在某个时刻停止测试用例执行并运行调试器通常很有用。为此,在您希望执行停止的行之前插入以下代码:

import pdb; pdb.set_trace()

例如:

def add(a, b):
    '''
    This is the test:
    >>> add(2, 2)
    5
    '''
    import pdb; pdb.set_trace()
    return a + b

对于此示例,下次运行 doctest 时,执行将在 return 语句之前停止,Python 调试器(pdb) 将启动。您将直接在笔记本中获得 pdb 提示,这将允许您检查 and 的值,a跨行b等。

breakpoint()注意:从 Python 3.7 开始,可以使用内置函数代替import pdb; pdb.set_trace().

我创建了一个Jupyter 笔记本来试验我刚刚描述的技术。你可以试试看粘合剂

于 2018-01-23T15:40:16.820 回答
17

我是( nteracttestbook下的一个项目)的作者和维护者。它是一个用于在 Jupyter Notebooks 中测试代码的单元测试框架。

testbook解决了您提到的所有三种方法,因为它允许将 Jupyter Notebooks 作为.py文件进行测试。

这是使用 testbook 编写的单元测试示例

考虑 Jupyter Notebook 中的以下代码单元:

def func(a, b):
    return a + b

您将使用 Python 文件中的 testbook 编写单元测试,如下所示:

import testbook


@testbook.testbook('/path/to/notebook.ipynb', execute=True)
def test_func(tb):
    func = tb.ref("func")

    assert func(1, 2) == 3

让我们知道 testbook 是否对您的用例有帮助!如果没有,请随时在GitHub 上提出问题:)


试卷特点

  • 为 Jupyter Notebooks 编写常规单元测试
  • 在单元测试之前执行所有或某些特定单元格
  • 跨多个测试共享内核上下文(使用 pytest 固定装置)
  • 将代码注入 Jupyter 笔记本
  • 适用于任何单元测试库 - unittest、pytest 或 nose

链接

PyPI GitHub 文档

于 2020-07-03T12:17:59.593 回答
5

在我看来,在 Jupyter 笔记本中进行单元测试的最佳方法是以下包: https ://github.com/JoaoFelipe/ipython-unittest

包文档中的示例:

%%unittest_testcase
def test_1_plus_1_equals_2(self):
    sum = 1 + 1
    self.assertEqual(sum, 2)

def test_2_plus_2_equals_4(self):
    self.assertEqual(2 + 2, 4)

Success
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
于 2018-12-18T12:47:56.443 回答
3

经过一番研究,我找到了自己的解决方案,我自己的测试代码如下所示

def red(text):
    print('\x1b[31m{}\x1b[0m'.format(text))

def assertEquals(a, b):
    res = a == b
    if type(res) is bool:
        if not res:
            red('"{}" is not "{}"'.format(a, b))
            return
    else:
        if not res.all():
            red('"{}" is not "{}"'.format(a, b))
            return

    print('Assert okay.')

它的作用是

  • 检查是否a等于b
  • 如果它们不同,它将以红色显示参数。
  • 如果它们相同,则表示“好的”。
  • 如果比较的结果是一个数组,它会检查是否all()为真。

我把这个功能放在我的笔记本上,我测试了这样的东西

def add(a, b):
    return a + b

assertEquals(add(1, 2), 3)
assertEquals(add(1, 2), 2)
assertEquals([add(1, 2), add(2, 2)], [3, 4])

---

Assert okay.
"3" is not "2"  # This is shown in red.
Assert okay.

这种方法的优点是

  • 我可以逐个单元地测试并在更改某些功能后立即查看结果。
  • doctest.testmod(verbose=True)如果我使用 doctest,我不需要添加类似我必须添加的额外代码。
  • 错误信息很简单。
  • 我可以自定义我的测试(断言)代码。
于 2018-10-09T20:33:48.180 回答
2

语境

由于我没有找到答案,因此我设法处理子/子文件夹中的所有单元测试,并考虑到:

使用测试数据在笔记本本身内写入断言(向笔记本添加噪音)。

这是运行存储在 jupyter notebook 的子/子文件夹中的单元测试的示例。

文件结构

  • some_folder/your_notebook.ipynb
  • some_folder/unit_test_folder/some_unit_test.py

单元测试文件内容

这将是some_unit_test.py文件的上下文:

# Python code to unittest the methods and agents
import unittest 
import os

import nbimporter
import your_notebook as eg

class TestAgent(unittest.TestCase): 

    def setUp(self): 
        print("Initialised unit test")

    # Unit test test two functions on a single line
    def test_nodal_precession(self):
        expected_state = 4
        returned_state = eg.add(2,2)
        self.assertEquals(expected_state,returned_state)

if __name__ == '__main__':
    main = TestAgent()

    # This executes the unit test/(itself)
    import sys
    suite = unittest.TestLoader().loadTestsFromTestCase(TestAgent)
    unittest.TextTestRunner(verbosity=4,stream=sys.stderr).run(suite)

Jupyter Notebook 文件内容

这将是调用和执行单元测试的单元:

# Some function that you want to do
def add(a, b):
    return a + b

!python "unit_test_folder/some_unite_test.py"
print("completed unit test inside the notebook")

运行单元测试

要运行单元测试,您可以只执行单元格,然后将单元测试的结果打印在 Jupyter Notebook 的单元格下方。或者您可以/some_folder使用 anaconda 浏览并运行 command: python unit_test_folder/some_unit_test.py,以在不打开笔记本的情况下运行命令(手动)。

于 2020-05-20T19:26:37.057 回答
1

如果您使用nbvalpytest-notebook插件,pytest您可以检查重新运行时单元格输出是否更改。

选项包括通过文件配置以及单元格注释(例如标记单元格以跳过)

于 2021-03-08T21:00:04.047 回答
1

如果你想测试一个类,你必须重新初始化一个 unittest 方法。

import unittest

class recom():
    def __init__(self):
        self.x = 1
        self.y = 2

class testRecom(unittest.TestCase):

    def setUp(self):
        self.inst = recom()

    def test_case1(self):
        self.assertTrue(self.inst.x == 1) 

    def test_case2(self):
        self.assertTrue(self.inst.y == 1) 

unittest.main(argv=[''], verbosity=2, exit=False)
    

它将产生以下输出:

test_case1 (__main__.testRecom) ... ok
test_case2 (__main__.testRecom) ... FAIL

======================================================================
FAIL: test_case2 (__main__.testRecom)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython-input-332-349860e645f6>", line 15, in test_case2
    self.assertTrue(self.inst.y == 1)
AssertionError: False is not true

----------------------------------------------------------------------
Ran 2 tests in 0.003s

FAILED (failures=1)
于 2021-05-11T15:51:56.560 回答
1

运行单个测试用例:

from unittest import TestCase, TextTestRunner, defaultTestLoader
class MyTestCase(TestCase):
    def test_something(self):
        self.assertTrue(True)
TextTestRunner().run(defaultTestLoader.loadTestsFromTestCase(MyTestCase))
于 2021-10-13T20:21:53.477 回答
0

这是我在学校学到的一个例子。这是假设您创建了一个名为“AnagramTest”的函数,它如下所示:

    from nose.tools import assert_equal

    class AnagramTest(object):

    def test(self,func):
        assert_equal(func('dog dog dog','gggdddooo'),True)
        assert_equal(func('xyz','zyx'),True)
        assert_equal(func('0123','1 298'),False)
        assert_equal(func('xxyyzz','xxyyz'),False)
        print("ALL TEST CASES PASSED")

# Run Tests
t = AnagramTest()
t.test(anagram)
于 2020-04-20T20:48:46.917 回答
0

鉴于您的上下文,最好在这样的笔记本单元格内编写文档测试construct_urlscrape_url

def construct_url(data):
    '''
    >>> data = fetch_test_data_from_somewhere()
    >>> construct_url(data)
    'http://some-constructed-url/'
    '''

    ... 
    <actual function>
    ...

然后你可以用底部的另一个单元格执行它们:

import doctest
doctest.testmod(verbose=True)

我还构建了treon,这是一个用于 Jupyter Notebooks 的测试库,可用于在 notebook 中执行 doctests 和 unittests。它还可以在新内核中从上到下执行笔记本并报告任何执行错误(健全性测试)。

于 2019-04-09T17:21:49.243 回答