-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from zopefoundation/doctestcase
doctests in TestCase classes
- Loading branch information
Showing
11 changed files
with
481 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,15 @@ | ||
[buildout] | ||
develop = . | ||
parts = test | ||
parts = py | ||
unzip = true | ||
|
||
[test] | ||
recipe = zc.recipe.testrunner | ||
eggs = | ||
zope.testing | ||
|
||
[py] | ||
recipe = zc.recipe.egg | ||
eggs = ${test:eggs} | ||
nose | ||
interpreter = py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
r"""Doctests in TestCase classes | ||
The original ``doctest`` unittest integration was based on | ||
``unittest`` test suites, which have fallen out of favor. This module | ||
provides a way to define doctests inside of unittest ``TestCase`` | ||
classes. It also provides better integration with unittest test | ||
fixtures, because doctests use setup provided by the containing test | ||
case class. It also provides access to unittest assertion | ||
methods. | ||
You can define doctests in 4 ways: | ||
- references to named files | ||
- strings | ||
- decorated functions with docstrings | ||
- reference to named files decorating test-specific setup functions | ||
.. some setup | ||
>>> __name__ = 'tests' | ||
Here are some examples:: | ||
from zope.testing import doctestcase | ||
import doctest | ||
import unittest | ||
g = 'global' | ||
class MyTest(unittest.TestCase): | ||
def setUp(self): | ||
self.a = 1 | ||
self.globs = dict(c=9) | ||
test1 = doctestcase.file('test1.txt', optionflags=doctest.ELLIPSIS) | ||
test2 = doctestcase.docteststring(''' | ||
>>> self.a, g, c | ||
(1, 'global', 9) | ||
''') | ||
@doctestcase.doctestmethod(optionflags=doctest.ELLIPSIS) | ||
def test3(self): | ||
''' | ||
>>> self.a, self.x, g, c | ||
(1, 3, 'global', 9) | ||
''' | ||
self.x = 3 | ||
@doctestcase.doctestfile('test4.txt') | ||
def test4(self): | ||
self.x = 5 | ||
In this example, 3 constructors were used: | ||
doctestfile (alias: file) | ||
doctestfile makes a file-based test case. | ||
This can be used as a decorator, in which case, the decorated | ||
function is called before the test is run, to provide test-specific | ||
setup. | ||
docteststring (alias string) | ||
docteststring constructs a doctest from a string. | ||
doctestmethod (alias method) | ||
doctestmethod constructs a doctest from a method. | ||
The method's docstring provides the test. The method's body provides | ||
optional test-specific setup. | ||
Note that short aliases are provided, which may be useful in certain | ||
import styles. | ||
Tests have access to the following data: | ||
- Tests created with the ``docteststring`` and ``doctestmethod`` | ||
constructors have access to the module globals of the defining | ||
module. | ||
- In tests created with the ``docteststring`` and ``doctestmethod`` | ||
constructors, the test case instance is available as the ``self`` | ||
variable. | ||
- In tests created with the ``doctestfile`` constructor, the test case | ||
instance is available as the ``test`` variable. | ||
- If a test case defines a globs attribute, it must be a dictionary | ||
and its contents are added to the test globals. | ||
The constructors accept standard doctest ``optionflags`` and | ||
``checker`` arguments. | ||
Note that the doctest IGNORE_EXCEPTION_DETAIL option flag is | ||
added to optionflags. | ||
""" | ||
import doctest | ||
import inspect | ||
import os | ||
import re | ||
import sys | ||
import types | ||
|
||
__all__ = ['doctestmethod', 'docteststring', 'doctestfile'] | ||
|
||
_parser = doctest.DocTestParser() | ||
|
||
def doctestmethod(test=None, optionflags=0, checker=None): | ||
"""Define a doctest from a method within a unittest.TestCase. | ||
The method's doc string provides the test source. Its body is | ||
called before the test and may perform test-specific setup. | ||
You can pass doctest option flags and a custon checker. | ||
Variables defined in the enclosing module are available in the test. | ||
If a test case defines a globs attribute, it must be a dictionary | ||
and its contents are added to the test globals. | ||
The test object is available as the variable ``self`` in the test. | ||
""" | ||
if test is None: | ||
return lambda test: _doctestmethod(test, optionflags, checker) | ||
|
||
return _doctestmethod(test, optionflags, checker) | ||
|
||
method = doctestmethod | ||
|
||
def _doctestmethod(test, optionflags, checker): | ||
doc = test.__doc__ | ||
if not doc: | ||
raise ValueError(test, "has no docstring") | ||
setup = test | ||
name = test.__name__ | ||
path = inspect.getsourcefile(test) | ||
lineno = inspect.getsourcelines(test)[1] | ||
|
||
fglobs = sys._getframe(3).f_globals | ||
|
||
def test_method(self): | ||
setup(self) | ||
_run_test(self, doc, fglobs.copy(), name, path, | ||
optionflags, checker, lineno=lineno) | ||
|
||
return test_method | ||
|
||
def docteststring(test, optionflags=0, checker=None): | ||
"""Define a doctest from a string within a unittest.TestCase. | ||
You can pass doctest option flags and a custon checker. | ||
Variables defined in the enclosing module are available in the test. | ||
If a test case defines a globs attribute, it must be a dictionary | ||
and its contents are added to the test globals. | ||
The test object is available as the variable ``self`` in the test. | ||
""" | ||
fglobs = sys._getframe(2).f_globals | ||
|
||
def test_string(self): | ||
_run_test(self, test, fglobs.copy(), '<string>', '<string>', | ||
optionflags, checker) | ||
|
||
return test_string | ||
|
||
string = docteststring | ||
|
||
def doctestfile(path, optionflags=0, checker=None): | ||
"""Define a doctest from a test file within a unittest.TestCase. | ||
The file path may be relative or absolute. If its relative (the | ||
common case), it will be interpreted relative to the directory | ||
containing the referencing module. | ||
You can pass doctest option flags and a custon checker. | ||
If a test case defines a globs attribute, it must be a dictionary | ||
and its contents are added to the test globals. | ||
The test object is available as the variable ``self`` in the test. | ||
The resulting object can be used as a function decorator. The | ||
decorated method is called before the test and may perform | ||
test-specific setup. (The decorated method's doc string is ignored.) | ||
""" | ||
base = os.path.dirname(os.path.abspath( | ||
sys._getframe(2).f_globals['__file__'] | ||
)) | ||
path = os.path.join(base, path) | ||
with open(path) as f: | ||
test = f.read() | ||
name = os.path.basename(path) | ||
|
||
def test_file(self): | ||
if isinstance(self, types.FunctionType): | ||
setup = self | ||
def test_file_w_setup(self): | ||
setup(self) | ||
_run_test(self, test, {}, name, path, optionflags, checker, | ||
'test') | ||
|
||
return test_file_w_setup | ||
|
||
_run_test(self, test, {}, name, path, optionflags, checker, 'test') | ||
|
||
return test_file | ||
|
||
file = doctestfile | ||
|
||
def _run_test(self, test, globs, name, path, | ||
optionflags, checker, testname='self', lineno=0): | ||
globs.update(getattr(self, 'globs', ())) | ||
globs[testname] = self | ||
optionflags |= doctest.IGNORE_EXCEPTION_DETAIL | ||
doctest.DocTestCase( | ||
_parser.get_doctest(test, globs, name, path, lineno), | ||
optionflags=optionflags, | ||
checker=checker, | ||
).runTest() |
Oops, something went wrong.