Тестирование кода учащихся в Jupyter с помощью unittest

#python #unit-testing #python-3.x #jupyter-notebook

#python #модульное тестирование #python-3.x #jupyter-записная книжка

Вопрос:

Я бы хотел, чтобы мои студенты могли проверять свой код, когда они записывают его в записную книжку Jupyter, вызывая функцию из импортированного модуля, который запускает unittest. Это работает нормально, если только функцию не нужно проверять по объектам, которые должны быть выбраны в глобальной области Notebook.

Вот мой check_test модуль:

 import unittest
from IPython.display import Markdown, display

def printmd(string):
    display(Markdown(string))

class Tests(unittest.TestCase):

    def check_add_2(self, add_2):
        val = 5
        self.assertAlmostEqual(add_2(val), 7)

    def check_add_n(self, add_n):
        n = 6
        val = 5
        self.assertAlmostEqual(add_n(val), 11)


check = Tests()
def run_check(check_name, func, hint=False):
    try:
        getattr(check, check_name)(func)
    except check.failureException as e:
        printmd('**<span style="color: red;">FAILED</span>**')
        if hint:
            print('Hint:',  e)
        return
    printmd('**<span style="color: green;">PASSED</span>**')
  

Если записная книжка:

 In [1]: def add_2(val):
            return val   2

In [2]: def add_n(val):
            return val   n

In [3]: import test_checks

In [4]: test_checks.run_check('check_add_2', add_2)
        PASSED

In [5]: test_checks.run_check('check_add_n', add_n)
        !!! ERROR !!!
  

Ошибка здесь не удивительна: add_n не знает о n I, определенном в check_add_n .

Итак, я подумал, что мог бы сделать что-то вроде:

 In [6]: def add_n(val, default_n=None):
            if default_n:
                n = default_n
            return val   n
  

в записной книжке, а затем прохождение n теста:

     def check_add_n(self, add_n):
        val = 5
        self.assertAlmostEqual(add_n(val, 6), 11)
  

Но это вызывает у меня UnboundLocalError головную боль в дальнейшем из-за назначения n , даже внутри if предложения: это, по-видимому, мешает ноутбуку n работать в глобальном масштабе, когда это необходимо.

Во избежание сомнений я не хочу настаивать на том, что n передается в качестве аргумента add_n : может быть много таких объектов, которые используются, но не изменяются тестируемой функцией, и я хочу, чтобы они разрешались во внешней области.

Есть идеи, как это сделать?

Комментарии:

1. Хотя я ответил на ваш вопрос, я нахожу эту add_n функцию довольно уродливой. Я бы предпочел, чтобы учащиеся написали функцию, подобную def make_adder(n): return lambda val: val n and then add_n = make_adder(n) , и сохранили ее n локальной…

2. Спасибо за ваш ответ — это большая помощь. Однако я не уверен, что более продвинутый код, который вы предлагаете, будет легко понять новичкам.

Ответ №1:

Вы можете import __main__ получить доступ к области notebook:

 import unittest
from IPython.display import Markdown, display

import __main__


def printmd(string):
    display(Markdown(string))

class Tests(unittest.TestCase):

    def check_add_2(self, add_2):
        val = 5
        self.assertAlmostEqual(add_2(val), 7)

    def check_add_n(self, add_n):
        __main__.n = 6
        val = 5
        self.assertAlmostEqual(add_n(val), 11)


check = Tests()
def run_check(check_name, func, hint=False):
    try:
        getattr(check, check_name)(func)
    except check.failureException as e:
        printmd('**<span style="color: red;">FAILED</span>**')
        if hint:
            print('Hint:',  e)
        return
    printmd('**<span style="color: green;">PASSED</span>**')
  

Это дает мне PASSED результат.


Это работает, потому что при выполнении файла python этот файл сохраняется в sys.modules качестве __main__ модуля. Именно поэтому if __name__ == '__main__': используется идиома. Можно импортировать такой модуль, и, поскольку он уже находится в кэше модуля, он не будет выполнять его повторно или что-либо еще.