Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing warnings with doctest

I'd like to use doctests to test the presence of certain warnings. For example, suppose I have the following module:

from warnings import warn

class Foo(object):
    """
    Instantiating Foo always gives a warning:

    >>> foo = Foo()
    testdocs.py:14: UserWarning: Boo!
      warn("Boo!", UserWarning)
    >>> 
    """

    def __init__(self):
        warn("Boo!", UserWarning)

If I run python -m doctest testdocs.py to run the doctest in my class and make sure that the warning is printed, I get:

testdocs.py:14: UserWarning: Boo!
  warn("Boo!", UserWarning)
**********************************************************************
File "testdocs.py", line 7, in testdocs.Foo
Failed example:
    foo = Foo()
Expected:
    testdocs.py:14: UserWarning: Boo!
      warn("Boo!", UserWarning)
Got nothing
**********************************************************************
1 items had failures:
   1 of   1 in testdocs.Foo
***Test Failed*** 1 failures.

It looks like the warning is getting printed but not captured or noticed by doctest. I'm guessing that this is because warnings are printed to sys.stderr instead of sys.stdout. But this happens even when I say sys.stderr = sys.stdout at the end of my module.

So is there any way to use doctests to test for warnings? I can find no mention of this one way or the other in the documentation or in my Google searching.

like image 612
Eli Courtwright Avatar asked Mar 10 '10 16:03

Eli Courtwright


People also ask

When should I not run the doctest example?

When specified, do not run the example at all. This can be useful in contexts where doctest examples serve as both documentation and test cases, and an example should be included for documentation purposes, but should not be checked.

How does doctest work in Python?

The Doctest Module finds patterns in the docstring that looks like interactive shell commands. The input and expected output are included in the docstring, then the doctest module uses this docstring for testing the processed output.

What is doctestfailure in doctest?

DocTestFailure defines the following attributes: The DocTest object that was being run when the example failed. The Example that failed. The example’s actual output. exception doctest. UnexpectedException (test, example, exc_info) ¶ An exception raised by DocTestRunner to signal that a doctest example raised an unexpected exception.

How to test the output of a docstring in doctest?

The input and expected output are included in the docstring, then the doctest module uses this docstring for testing the processed output. After parsing through the docstring, the parsed text is executed as python shell commands and the result is compared with the expected outcome fetched from the docstring.


2 Answers

This isn't the most elegant way to do it, but it works for me:

from warnings import warn

class Foo(object):
    """
    Instantiating Foo always gives a warning:

    >>> import sys; sys.stderr = sys.stdout
    >>> foo = Foo() # doctest:+ELLIPSIS
    /.../testdocs.py:14: UserWarning: Boo!
      warn("Boo!", UserWarning)
    """

    def __init__(self):
        warn("Boo!", UserWarning)

if __name__ == '__main__':
    import doctest
    doctest.testmod()

This presumably won't work on Windows, though, since the path reported in the UserWarning output must start with a slash the way I've written this test. You may be able to figure out some better incantation of the ELLIPSIS directive, but I could not.

like image 185
Will McCutchen Avatar answered Oct 19 '22 14:10

Will McCutchen


The Testing Warnings sections of the Python documentation is dedicated to this topic. However, to summarize, you have two options:

(A) Use the catch_warnings context manager

This is recommended course in the official documentation. However, the catch_warnings context manager only came into existence with Python 2.6.

import warnings

def fxn():
    warnings.warn("deprecated", DeprecationWarning)

with warnings.catch_warnings(record=True) as w:
    # Cause all warnings to always be triggered.
    warnings.simplefilter("always")
    # Trigger a warning.
    fxn()
    # Verify some things
    assert len(w) == 1
    assert issubclass(w[-1].category, DeprecationWarning)
    assert "deprecated" in str(w[-1].message)

(B) Upgrade Warnings to Errors

If the warning hasn't been seen before— and thus was registered in the warnings registry— then you can set warnings to raise exceptions and catch it.

import warnings


def fxn():
    warnings.warn("deprecated", DeprecationWarning)


if __name__ == '__main__':
    warnings.simplefilter("error", DeprecationWarning)

    try:
        fxn()
    except DeprecationWarning:
        print "Pass"
    else:
        print "Fail"
    finally:
        warnings.simplefilter("default", DeprecationWarning)
like image 35
Scott Robinson Avatar answered Oct 19 '22 13:10

Scott Robinson