Search code examples
pythondoctest

Python doctest: have a common initialization for several tests


I want to have a doctest for a class, where the use of the class needs a somehow lengthy setup. Like:

class MyClass
    def __init__(self, foo):
        self.foo = foo

    def bar(self):
        """Do bar

            >>> # do a multiline generation of foo
                ... ...
            >>> myoby = MyClass(foo)
            >>> print(myobj.bar())
            BAR
        """
        …

    def foobar(self):
        """Do foobar

            >>> # do a multiline generation of foo
                ... ...
            >>> myoby = MyClass(foo)
            >>> print(myobj.foobar())
            FOOBAR
        """
        …

My real case has ~8 methods that I want to document and pytest. Repeating the generation of foo everywhere contradicts the DRY principle, and also generated quite lengthy and unreadable documentation. Is there a way to avoid it?

Optimally would be like

class MyClass
    """My class

    An example way to create the 'foo' argument is

        >>> # do a multiline generation of foo
            ... ...
    """
    def __init__(self, foo):
        self.foo = foo

    def bar(self):
        """Do bar

            >>> myoby = MyClass(foo)
            >>> print(myobj.bar())
            BAR
        """
        …

    def foobar(self):
        """Do foobar

            >>> myoby = MyClass(foo)
            >>> print(myobj.foobar())
            FOOBAR
        """
        …

Solution

  • After some struggling, I found the following solution, (ab)using a submodule of my own module:

    class MyClass
        """My class
    
        An example way to create the 'foo' argument is::
    
            >>> # do a multiline generation of foo
            >>> foo = ...
    
        .. only:: doctest
    
            >>> import mymodule.tests
            >>> mymodule.tests.foo = foo
    
        """
        def __init__(self, foo):
            self.foo = foo
    
        def bar(self):
            """Do bar
    
            .. only:: doctest
    
                >>> import mymodule.tests
                >>> foo = mymodule.tests.foo
    
            Here is a good example, using the initialization above::
    
                >>> myoby = MyClass(foo)
                >>> print(myobj.bar())
                BAR
            """
            …
    

    The "tricks":

    • I abuse my mypackage.tests submodule to store the results of the initialization. This works, since the modules are not re-initialized in subsequent tests

    • Doctest (at least in pytest) doesn't care about code blocks, it just looks for the appropriate patterns and indentions. Any block is fine, even a conditional one (.. only::). The condition (doctest) is arbitrary, it just needs to be evaluated to false so that the block isn't displayed.

    I am still not sure how robust this is with respect to future developments of doctest/pytest and sphinxdoc.