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

.. 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')
    >>> 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


    >>> for _, e in result.errors:
    ...     print(e); print

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 maye 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 it's 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.

.. Let's look at some failure cases:

    >>> class MyTest(unittest.TestCase):
    ...
    ...     test2 = doctestcase.string('''
    ...     >>> 1
    ...     1
    ...     >>> 1 + 1
    ...     1
    ...     ''')
    ...
    ...     @doctestcase.method
    ...     def test3(self):
    ...         '''
    ...         >>> self.x
    ...         3
    ...         >>> 1 + 1
    ...         1
    ...         '''
    ...         self.x = 3
    ...
    ...     @doctestcase.file('test4f.txt')
    ...     def test4(self):
    ...         self.x = 5

    >>> suite = loader.loadTestsFromTestCase(MyTest)
    >>> result = suite.run(unittest.TextTestResult(sys.stdout, True, 1))
    FFF
    >>> for c, e in result.failures:
    ...     print(e) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
    Traceback (most recent call last):
      ...
    ...: Failed doctest test for <string>
      File "<string>", line 0, in <string>
    <BLANKLINE>
    ----------------------------------------------------------------------
    File "<string>", line 4, in <string>
    Failed example:
        1 + 1
    Expected:
        1
    Got:
        2
    <BLANKLINE>
    <BLANKLINE>
    Traceback (most recent call last):
      ...
    ...: Failed doctest test for test3
      File "None", line 10, in test3
    <BLANKLINE>
    ----------------------------------------------------------------------
    Line 4, in test3
    Failed example:
        1 + 1
    Expected:
        1
    Got:
        2
    <BLANKLINE>
    <BLANKLINE>
    Traceback (most recent call last):
    ...
    ...: Failed doctest test for test4f.txt
      File "...test4f.txt", line 0, in txt
    <BLANKLINE>
    ----------------------------------------------------------------------
    File "...test4f.txt", line 3, in test4f.txt
    Failed example:
        1 + 1
    Expected:
        1
    Got:
        2
    <BLANKLINE>
    <BLANKLINE>

.. Verify setting optionflags and checker

    >>> class EasyChecker:
    ...     def check_output(self, want, got, optionflags):
    ...         return True
    ...     def output_difference(self, example, got, optionflags):
    ...         return ''

    >>> class MyTest(unittest.TestCase):
    ...
    ...     test2 = doctestcase.string('''
    ...     >>> 1
    ...     2
    ...     ''', checker=EasyChecker())
    ...
    ...     @doctestcase.method(optionflags=doctest.ELLIPSIS)
    ...     def test3(self):
    ...         '''
    ...         >>> 'Hello'
    ...         '...'
    ...         '''
    ...
    ...     @doctestcase.file('test4e.txt', optionflags=doctest.ELLIPSIS)
    ...     def test4(self):
    ...         self.x = 5
    >>> suite = loader.loadTestsFromTestCase(MyTest)
    >>> result = suite.run(unittest.TextTestResult(sys.stdout, True, 2))
    test2 (tests.MyTest) ... ok
    test3 (tests.MyTest) ... ok
    test4 (tests.MyTest) ... ok
