Модульные тесты для функций в ноутбуке Jupyter?
У меня есть блокнот 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 для проверки содержимого ячеек (не думайте, что это работает, потому что содержимое ячеек изменится).
10 ответов
Можно использовать стандартные инструменты тестирования Python, такие как doctest или unittest, непосредственно в ноутбуке.
Doctest
Ячейка тетради с функцией и тестовым набором в строке документации:
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 прямо в записной книжке, которое позволит вам проверить значения a
а также b
, переступить черту и т. д.
Я создал блокнот Jupyter для экспериментов с методами, которые я только что описал.
Я автор и сопровождающий testbook
(проект под взаимодействием). Это среда модульного тестирования для тестирования кода в Jupyter Notebooks.
testbook
обращается ко всем трем упомянутым вами подходам, поскольку позволяет тестировать Jupyter Notebooks как .py
файлы.
Вот пример модульного теста, написанного с использованием тестовой книги
Рассмотрим следующую ячейку кода в Jupyter Notebook:
def func(a, b):
return a + b
Вы должны написать модульный тест с использованием тестовой книги в файле Python следующим образом:
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
- Выполнить все или некоторые определенные ячейки перед модульным тестом
- Совместное использование контекста ядра в нескольких тестах (с использованием приспособлений pytest)
- Внедрить код в записные книжки Jupyter
- Работает с любой библиотекой модульного тестирования - unittest, pytest или носом
Ссылки
Запуск одного тестового примера:
from unittest import TestCase, TextTestRunner, defaultTestLoader
class MyTestCase(TestCase):
def test_something(self):
self.assertTrue(True)
TextTestRunner().run(defaultTestLoader.loadTestsFromTestCase(MyTestCase))
На мой взгляд, лучший способ провести юнит-тесты в блокноте 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
После небольшого исследования я нашел свое собственное решение, где у меня есть свой собственный код тестирования, похожий на этот
def red(text):
print('\x1b[31m{}\x1b[0m'.format(text))
def assertEqual(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
assertEqual(add(1, 2), 3)
assertEqual(add(1, 2), 2)
assertEqual([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. - Сообщения об ошибках просты.
- Я могу настроить свой тестовый (утверждающий) код.
Контекст
Поскольку я не нашел ответа, мне удалось получить работу со всеми модульными тестами в дочерней / подпапке и с учетом:
Пишите утверждения в самом ноутбуке, используя тестовые данные (добавляет шум в ноутбук).
Это пример запуска модульных тестов, которые хранятся в дочерней / подпапке из записной книжки jupyter.
Файловая структура
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
с анакондой и запустите команду: python unit_test_folder/some_unit_test.py
, чтобы запустить команду, не открывая записную книжку (вручную).
Если вы хотите протестировать класс, вам придется повторно запустить метод 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)
Если вы используете nbval
или же pytest-notebook
плагины для
pytest
вы можете проверить, что выходы ячеек не меняются при повторном запуске.
Опции включают конфигурацию через файл, а также комментарии к ячейкам (например, пометить ячейки, которые нужно пропустить)
Учитывая ваш контекст, лучше всего писать тесты дляconstruct_url
& scrape_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, которую можно использовать для выполнения doctests и unittests в ноутбуках. Он также может выполнять записные книжки сверху вниз в новом ядре и сообщать о любых ошибках выполнения (тестирование работоспособности).
Вот пример, который я выучил в школе. Предполагается, что вы создали функцию под названием "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)