From 5886813af884f60090c7b095cc8069d3e97d4e30 Mon Sep 17 00:00:00 2001 From: Jim Fulton Date: Mon, 31 Aug 2015 19:01:58 -0400 Subject: [PATCH] make doctestfiles into a class decorator --- CHANGES.rst | 8 ++- src/zope/testing/doctestcase.py | 22 +++---- src/zope/testing/doctestcase.txt | 108 ++++++++++--------------------- 3 files changed, 48 insertions(+), 90 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 4b8919e..67077c8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -19,11 +19,15 @@ Changes - Automatically assign test class members. So rather than:: - test_foo = doctestcase.doctestfile('foo.txt') + class MYTests(unittest.TestCase): + ... + test_foo = doctestcase.doctestfile('foo.txt') You can use:: - doctestcase.doctestfiles('foo.txt') + @doctestcase.doctestfiles('foo.txt') + class MYTests(unittest.TestCase): + ... 4.4.0 (2015-07-16) ------------------ diff --git a/src/zope/testing/doctestcase.py b/src/zope/testing/doctestcase.py index 8a98c9f..9551772 100644 --- a/src/zope/testing/doctestcase.py +++ b/src/zope/testing/doctestcase.py @@ -229,10 +229,10 @@ def test_file_w_setup(self): file = doctestfile def doctestfiles(*paths, **kw): - """Define doctests from test files within a unittest.TestCase. + """Define doctests from test files in a decorated class. Multiple files can be specified. A member is added to the - surrounding class for each file. + decorated class for each file. The file paths may be relative or absolute. If relative (the common case), they will be interpreted relative to the directory @@ -245,22 +245,18 @@ def doctestfiles(*paths, **kw): The test object is available as the variable ``test`` 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.) + The resulting object must be used as a class decorator. """ - locals = sys._getframe(1).f_locals - for path in paths: - locals[name_from_path(path)] = doctestfile(path, **kw) - - def doctestfiles_w_setup(func): + def doctestfiles_(class_): for path in paths: name = name_from_path(path) - test = doctestfile(path, **kw)(func) - locals[name] = test + test = doctestfile(path, **kw) test.__name__ = name + setattr(class_, name, test) + + return class_ - return doctestfiles_w_setup + return doctestfiles_ files = doctestfiles diff --git a/src/zope/testing/doctestcase.txt b/src/zope/testing/doctestcase.txt index 7ee7dfa..19d4e49 100644 --- a/src/zope/testing/doctestcase.txt +++ b/src/zope/testing/doctestcase.txt @@ -4,12 +4,11 @@ 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. +classes. It provides better integration with unittest test fixtures, +because doctests use setup provided by the containing test case +class. It provides access to unittest assertion methods. -You can define doctests in 4 ways: +You can define doctests in multiple ways: - references to named files @@ -19,6 +18,8 @@ You can define doctests in 4 ways: - reference to named files decorating test-specific setup functions +- reference to named files decorating a test class + .. some setup >>> __name__ = 'tests' @@ -30,6 +31,7 @@ Here are some examples:: >>> import unittest >>> g = 'global' + >>> class MyTest(unittest.TestCase): ... ... def setUp(self): @@ -54,23 +56,36 @@ Here are some examples:: ... @doctestcase.doctestfile('test4.txt') ... def test4(self): ... self.x = 5 + + >>> import sys + + >>> @doctestcase.doctestfiles('loggingsupport.txt', 'renormalizing.txt') + ... class MoreTests(unittest.TestCase): ... - ... doctestcase.doctestfiles('loggingsupport.txt', 'renormalizing.txt') + ... def setUp(self): + ... def print_(*args): + ... sys.stdout.write(' '.join(map(str, args))+'\n') + ... self.globs = dict(print_ = print_) + .. We can run these tests with the ``unittest`` test runner. >>> loader = unittest.TestLoader() - >>> suite = loader.loadTestsFromTestCase(MyTest) - >>> import sys >>> sys.stdout.writeln = lambda s: sys.stdout.write(s+'\n') + >>> suite = loader.loadTestsFromTestCase(MyTest) >>> result = suite.run(unittest.TextTestResult(sys.stdout, True, 3)) test1 (tests.MyTest) ... ok test2 (tests.MyTest) ... ok test3 (tests.MyTest) ... ok test4 (tests.MyTest) ... ok - test_loggingsupport (tests.MyTest) ... FAIL - test_renormalizing (tests.MyTest) ... FAIL + >>> for _, e in result.errors: + ... print(e); print + + >>> suite = loader.loadTestsFromTestCase(MoreTests) + >>> result = suite.run(unittest.TextTestResult(sys.stdout, True, 3)) + test_loggingsupport (tests.MoreTests) ... ok + test_renormalizing (tests.MoreTests) ... ok >>> for _, e in result.errors: ... print(e); print @@ -97,70 +112,16 @@ Here are some examples:: >>> MyTest.test4.filename 'test4.txt' - >>> MyTest.test_loggingsupport.__name__ - 'test_loggingsupport' - >>> MyTest.test_loggingsupport.filename - 'loggingsupport.txt' - >>> (MyTest.test_loggingsupport.filepath == - ... os.path.join(os.path.dirname(zope.testing.__file__), - ... 'loggingsupport.txt')) - True - - Need setup: - - >>> import sys - >>> class MyTest(unittest.TestCase): - ... - ... def setUp(self): - ... self.a = 1 - ... self.globs = dict(c=9) - ... - ... test1 = doctestcase.file('test-1.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 - ... - ... @doctestcase.files('loggingsupport.txt', 'renormalizing.txt') - ... def setup_print(self): - ... def print_(*args): - ... sys.stdout.write(' '.join(map(str, args))+'\n') - ... self.globs['print_'] = print_ - - >>> suite = loader.loadTestsFromTestCase(MyTest) - >>> result = suite.run(unittest.TextTestResult(sys.stdout, True, 3)) - test1 (tests.MyTest) ... ok - test2 (tests.MyTest) ... ok - test3 (tests.MyTest) ... ok - test4 (tests.MyTest) ... ok - test_loggingsupport (tests.MyTest) ... ok - test_renormalizing (tests.MyTest) ... ok - - Check meta data with setup function: - - >>> MyTest.test_loggingsupport.__name__ + >>> MoreTests.test_loggingsupport.__name__ 'test_loggingsupport' - >>> MyTest.test_loggingsupport.filename + >>> MoreTests.test_loggingsupport.filename 'loggingsupport.txt' - >>> (MyTest.test_loggingsupport.filepath == + >>> (MoreTests.test_loggingsupport.filepath == ... os.path.join(os.path.dirname(zope.testing.__file__), ... 'loggingsupport.txt')) True -In this example, 4 constructors were used: +In these examples, 4 constructors were used: doctestfile (alias: file) doctestfile makes a file-based test case. @@ -170,14 +131,11 @@ doctestfile (alias: file) setup. doctestfiles (alias: files) - doctestfiles makes file-based test cases and assigns them to the class. + doctestfiles makes file-based test cases and assigns them to the + decorated class. - Multile files can be specified and the resulting doctests are inserted into - the class dictionary. - - This can be used as a decorator, in which case, the decorated - function is called before each test is run, to provide test-specific - setup. + Multile files can be specified and the resulting doctests are added + as members of the decorated class. docteststring (alias string) docteststring constructs a doctest from a string.