it-swarm.com.ru

Модульные тесты для функций в ноутбуке 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 для проверки содержимого ячеек (не думайте, что это работает, потому что содержимое ячеек изменится). 
15
Richard

Можно использовать стандартные инструменты тестирования 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 для экспериментов с методами, которые я только что описал.

14
SergiyKolesnikov

На мой взгляд, лучший способ провести юнит-тесты в блокноте 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
1
Michael D

После небольшого исследования я нашел свое собственное решение, где у меня есть свой собственный код тестирования, похожий на этот

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.')

Что это делает

  • Проверьте, равен ли ab.
  • Если они разные, аргументы отображаются красным цветом.
  • Если они одинаковые, это говорит «хорошо».
  • Если результатом сравнения является массив, он проверяет, истинно ли 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.
  • Сообщения об ошибках просты.
  • Я могу настроить свой тестовый (утверждающий) код.
0
Sanghyun Lee