I'd like my students to be able to check their code as they write it in a Jupyter Notebook by calling a function from an imported module which runs a unittest. This works fine unless the function needs to be checked against objects which are to be picked up in the global scope of the Notebook.
Here's my check_test
module:
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>**')
If the Notebook is:
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 !!!
The error here is not suprising: add_n
doesn't know about the n
I defined in check_add_n
.
So I got to thinking I could do something like:
In [6]: def add_n(val, default_n=None):
if default_n:
n = default_n
return val + n
in the Notebook, and then passing n
in the test:
def check_add_n(self, add_n):
val = 5
self.assertAlmostEqual(add_n(val, 6), 11)
But this is causing me UnboundLocalError
headaches down the line because of the assignment of n
, even within an if
clause: this is apparently stopping the Notebook from picking up n
in global scope when it's needed.
For the avoidance of doubt, I don't want insist that n
is passed as an argument to add_n
: there could be many such objects used but not changed by the function being tested and I want them resolved in the outer scope.
Any ideas how to go about this?
You can import __main__
to access the notebook scope:
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>**')
This gives me a PASSED
output.
This works because when you execute a python file that file is stored in sys.modules
as the __main__
module. This is precisely why the if __name__ == '__main__':
idiom is used. It is possible to import such module and since it is already in the module cache it will not re-execute it or anything.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With