Home > OS >  Why does this doctest pass in Pycharm, but not on the command line?
Why does this doctest pass in Pycharm, but not on the command line?

Time:09-22

I am having a very strange unit test failure in Python 3.9 doctests. Normally I'd have to sanitize my problem, but this is in pretty low level code so I can post it here in its entirety.

I can run this doctest in Pycharm without issue. When I run it on the command line, I get a failure that makes no sense. Any help would be appreciated.

quadratic_solver.py

import math
import numpy as np
import doctest


def solve(
        a: float,
        b: float,
        c: float):
    """
    :return: the roots of the quadratic arranged in a 0-2 floating point value-long array

    >>> roots = solve(2., -1., 100.) # noRoots
    >>> len(roots)
    0
    >>> roots = solve(1., 2., 1.) # oneRoot
    >>> len(roots)
    1
    >>> round(roots[0], 9)
    -1.0
    >>> roots = solve(1., 4., 1.) # twoRoots_1
    >>> len(roots)
    2
    >>> round(roots[0], 9)
    -3.732050808
    >>> round(roots[1], 9)
    -0.267949192
    >>> roots = solve(-9., 61., 19.) # twoRoots_2
    >>> len(roots)
    2
    >>> round(roots[0], 9)
    -0.298343001
    >>> round(roots[1], 9)
    7.076120779
    """
    # https://math.stackexchange.com/questions/866331/numerically-stable-algorithm-for-solving-the-quadratic-equation-when-a-is-very
    desc = b**2 - 4. * a * c
    if desc < 0.:
        # no roots
        return np.array([])
    elif desc == 0.:
        # one root
        root = -b / (2. * a)
        return np.array([root])
    else:
        d = math.sqrt(b**2 - 4. * a * c)

        root1 = (-b   d) / (2. * a)
        root2 = (-b - d) / (2. * a)

        if root1 < root2:
            return np.array([root1, root2])
        else:
            return np.array([root2, root1])


doctest.testmod()

Error message when running python -m doctest $PATH_TO_FILE$/quadratic_solver.py:

**********************************************************************
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/doctest.py", line 1820, in __main__.DebugRunner
Failed example:
    runner.run(test)
Expected:
    Traceback (most recent call last):
    ...
    doctest.UnexpectedException: <DocTest foo from foo.py:0 (2 examples)>
Got:
    Traceback (most recent call last):
      File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/doctest.py", line 1336, in __run
        exec(compile(example.source, filename, "single",
      File "<doctest __main__.DebugRunner[15]>", line 1, in <module>
        runner.run(test)
      File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/doctest.py", line 1844, in run
        r = DocTestRunner.run(self, test, compileflags, out, False)
      File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/doctest.py", line 1483, in run
        return self.__run(test, compileflags, out)
      File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/doctest.py", line 1388, in __run
        self.report_unexpected_exception(out, test, example,
      File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/doctest.py", line 1850, in report_unexpected_exception
        raise UnexpectedException(test, example, exc_info)
    UnexpectedException: <DocTest foo from foo.py:0 (2 examples)>
**********************************************************************

CodePudding user response:

The problem here is the line doctest.testmod().

PyCharm just detects that there are doctests in the Python module, and runs them using its own doctest runner, so it just ignores that line.

If you call the doctester from the command line, it does basically the same: it detects all doctests and runs them, but additionally it also tries to doctest the doctest execution line. You can see this if you use verbose output:

python -m doctest -v $PATH_TO_FILE$/quadratic_solver.py

This shows you that all the doctests are correctly run, and afterwards the test in doctest are executed. One of the classes in doctest there (namely DebugRunner) has itself a doctest that fails (not sure if this is a bug, or just not intended to be run this way).

The reason for this is that you don't protect the testrunner code from execution if called via a library (using if __name__ == "__main__"), in this case from the doctester.

So to fix this, you have two possibilities:

  • remove the line that calls the doctests; this will make it work with doctest on the command line
  • use:
if __name__ == "__main__":
    doctest.testmod()

This will make it work with both the call via doctest:

python -m doctest $PATH_TO_FILE$/quadratic_solver.py

And the call that just executes the doctest line in the module:

python $PATH_TO_FILE$/quadratic_solver.py

Note that the second variant would also work with your current code, but it would also mean that the doctests are exceuted any time you import the library from elsewhere, which you certainly don't want.

  • Related