I use nose for test collection and I also want to use its doctest plugin. I have a module that needs a fixture in order to be importable. Therefore, I cannot use nose's module fixtures, since they are loaded from the module under test. Is there a way to specify module fixtures for nose-doctest outside of the module?
For some use-cases, an option would be to detect being run under doctest and to apply the fixture at the beginning of the module. I'd be interested to hear answers for this use-case as-well.
However, there are situations where this cannot work: When the import fails due to a SyntaxError
, no module code is ever run. In my case, I'm mostly developing code that is both compatible with python 2 and python 3 (without 2to3
). There are a few python 3 specific modules however, which simply should not be inspected at all by nose, when run under python 2. What would my best option be here?
EDIT: MWE (for the SyntaxError
situation)
I have a package with many small modules, some of them use python 3 syntax. Here's the package structure:
~/pckg/
__init__.py
py3only.py
... (other modules)
tests/
test_py3only.py
Some tests are written as unittest.TestCase
, but I also want the code examples in the docstrings tested. ~/pckg/__init__.py
is empty.
~/pckg/py3only.py:
def fancy_py3_func(a:"A function argument annotation (python 3 only syntax)"):
""" A function using fancy syntax doubling it's input.
>>> fancy_py3_func(4)
8
"""
return a*2
~/pckg/tests/test_py3only.py:
import sys, unittest
def setup_module():
if sys.version_info[0] < 3:
raise unittest.SkipTest("py3only unavailable on python "+sys.version)
class TestFancyFunc(unittest.TestCase):
def test_bruteforce(self):
from pckg.py3only import fancy_py3_func
for k in range(10):
self.assertEqual(fancy_py3_func(k),2*k)
Testing on python 3, everything gets tested and passes (run from the enclosing folder, e.g. ~
):
~ nosetests3 -v --with-doctest pckg
Doctest: pckg.py3only.fancy_py3_func ... ok
test_bruteforce (test_py3only.TestFancyFunc) ... ok
On python 2, the module fixture of ~/pckg/tests/test_py2only.py
properly detects the situation and skips the test. However, we get a SyntaxError
from ~/pckg/py3only.py
:
~ nosetests -v --with-doctest pckg
Failure: SyntaxError (invalid syntax (py3only.py, line 1)) ... ERROR
SKIP: py3only unavailable on python 2.7.6 (default, Mar 22 2014, 22:59:56)
A function similar to ~/pckg/tests/test_py3only.py:setup_module()
could solve that problem, if I could get nose
to run that code before it's doctest plugin even attempts to import that module.
It looks like my best bet is to write a proper top-level test script that handles the collection of the tests...
Specific test files, directories, classes or methods can be excluded using nose-exclude nose plugin. It has --exclude-*
options.
To handle missing modules, you have to patch sys.modules
using mock
.
F.e, There's a Calc
class in mycalc
module, but I don't have access to it because it's missing. And there are two more modules, mysuper_calc
and mysuper_calc3
, the latter is Python 3 specific. These two modules import mycalc
and mysuper_calc3
shouldn't be tested under Python 2. How to doctest them out of module, that's in plain text file? I assume this is OP's situation.
calc/mysuper_calc3.py
from sys import version_info
if version_info[0] != 3:
raise Exception('Python 3 required')
from mycalc import Calc
class SuperCalc(Calc):
'''This class implements an enhanced calculator
'''
def __init__(self):
Calc.__init__(self)
def add(self, n, m):
return Calc.add(self, n, m)
calc/mysuper_calc.py
from mycalc import Calc
class SuperCalc(Calc):
'''This class implements an enhanced calculator
'''
def __init__(self):
Calc.__init__(self)
def add(self, n, m):
return Calc.add(self, n, m)
Now to mock out mycalc
,
>>> from mock import Mock, patch
>>> mock = Mock(name='mycalc')
Module mycalc
has class Calc
which has method add
. I test SuperCalc
instance add
method with 2+3
.
>>> mock.Calc.add.return_value = 5
Now patch sys.modules
and mysuper_calc3
can be conditionally imported within the with
block.
>>> with patch.dict('sys.modules',{'mycalc': mock}):
... from mysuper_calc import SuperCalc
... if version_info[0] == 3:
... from mysuper_calc3 import SuperCalc
calc/doctest/mysuper_calc_doctest.txt
>>> from sys import version_info
>>> from mock import Mock, patch
>>> mock = Mock(name='mycalc')
>>> mock.Calc.add.return_value = 5
>>> with patch.dict('sys.modules',{'mycalc': mock}):
... from mysuper_calc import SuperCalc
... if version_info[0] == 3:
... from mysuper_calc3 import SuperCalc
>>> c = SuperCalc()
>>> c.add(2,3)
5
The file mysuper_calc_doctest.txt
has to be alone in its own directory otherwise nosetests
searches for doctest
in non-test modules.
PYTHONPATH=.. nosetests --with-doctest --doctest-extension=txt --verbosity=3
Doctest: mysuper_calc_doctest.txt ... ok
Ran 1 test in 0.038s
OK
A wrapper around nosetests
to detect Python 3 which passes .py files without syntax errors to nosetests
mynosetests.py
import sys
from subprocess import Popen, PIPE
from glob import glob
f_list = []
py_files = glob('*py')
try:
py_files.remove(sys.argv[0])
except ValueError:
pass
for py_file in py_files:
try:
exec open(py_file)
except SyntaxError:
continue
else:
f_list.append(py_file)
proc = Popen(['nosetests'] + sys.argv[1:] + f_list,stdout=PIPE, stderr=PIPE)
print('%s\n%s' % proc.communicate())
sys.exit(proc.returncode)
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