Doctest 失败,退出代码为零

问题描述 投票:0回答:3

在我的测试代码中,我的 doctest 失败,但脚本以零返回值退出,这导致 CI 运行通过,这不是预期的。

这是 doctest 模块的正确行为吗?

我的脚本结尾为:

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

输出如下:

**********************************************************************
File "test/test.py", line 7, in __main__
Failed example:
    f(1,0)
Expected:
    -----
    type: <type 'exceptions.ZeroDivisionError'>
    value: integer division or modulo by zero
    x
    -----
Got:
    -----
    type: <type 'exceptions.ZeroDivisionError'>
    value: integer division or modulo by zero
    -----
**********************************************************************
1 items had failures:
   1 of   1 in __main__
***Test Failed*** 1 failures.
tux@iPad:~/programming/exception-notifier(fix-travis)(0)$ echo $?
0
python doctest
3个回答
8
投票

@fossilet 的答案适用于正确破坏测试失败的构建,但它会在 doctest 能够将任何内容写入控制台之前引发异常。这使得您的日志对于识别问题的作用大大减弱。

另一种方法是致电

sys.exit(doctest.testmod()[0])

这使得进程退出代码等于失败的测试数。您的 CI 工具应将非零退出代码解释为失败的构建。但 doctest 输出仍然会到达控制台。


5
投票

我发现使用

doctest.testmod(raise_on_error=True)
会导致测试失败时引发异常,这会导致脚本以非零代码退出。

Python 文档在这里:

可选参数

raise_on_error
默认为 false。如果属实,则 第一次失败或意外异常时引发异常 一个例子。这允许对故障进行事后调试。默认 行为是继续运行示例。


0
投票

doctest.testmod
函数返回包含失败和测试计数的对:

(failure_count, test_count)

因此,只要

failure_count
不同于/大于零,我们就可以使用不同于零的退出代码退出:

def foo():
    """
    This amazing function returns 1337

    >>> foo()
    1
    """ 

    return 1337

if __name__ == "__main__":
    import doctest
    import sys
    (failure_count, _) = doctest.testmod()
    if failure_count:
        sys.exit(1)

这有效:

$ python example.py
**********************************************************************
File "example.py", line 5, in __main__.foo
Failed example:
    foo()
Expected:
    1
Got:
    1337
**********************************************************************
1 items had failures:
   1 of   1 in __main__.foo
***Test Failed*** 1 failures.

$ echo $?
1

它打印错误并返回错误退出代码 (1)。 如果从 Makefile 或 CI 中运行测试,这非常有用:

make
中止执行并报告错误; CI 构建理所当然地失败了。

我喜欢这个解决方案的一点是 成功是沉默的。 如果我们修复文档测试,这就是输出:

$ python example.py

$ echo $?
0

不返回错误计数

您可能会想写:

if __name__ == "__main__":
    import doctest
    import sys
    sys.exit(doctest.testmod()[0]) # XXX: don't!

不要

sys.exit
的文档指出: “大多数系统要求[退出代码]在 0-127 范围内, 否则会产生未定义的结果。”

在 Linux 上,退出代码以 256 为模。 因此,如果有人完全有 256 个文档测试错误, 将获得成功 (0) 退出代码! 对于 256 的所有倍数都是如此:512、768、1024... 在其他系统上,您最终会依赖未定义的行为: 也许当你有超过一百个错误时你总是会获得成功(0),这与你想要的相反。

替代方案:使用
raise_on_error=True

我们可以使用

raise_on_error
参数来
doctest.testmod
这样它会在测试失败时抛出错误:

    doctest.testmod(raise_on_error=True)

但是,这会打印一个不是很有用的回溯:

$ python example.py
Traceback (most recent call last):
  File "example.py", line 13, in <module>
    doctest.testmod(raise_on_error=True)
  File "/usr/lib/python3.12/doctest.py", line 1998, in testmod
    runner.run(test)
  File "/usr/lib/python3.12/doctest.py", line 1886, in run
    r = DocTestRunner.run(self, test, compileflags, out, False)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/doctest.py", line 1525, in run
    return self.__run(test, compileflags, out)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/doctest.py", line 1426, in __run
    self.report_failure(out, test, example, got)
  File "/usr/lib/python3.12/doctest.py", line 1895, in report_failure
    raise DocTestFailure(test, example, got)
doctest.DocTestFailure: <DocTest __main__.foo from example.py:1 (1 example)>

可以激活详细信息 (

verbose=True
) 以在回溯之前获得摘要。 然而,无用的回溯仍然会污染输出,并且成功不再沉默。 所以我个人不太喜欢这种选择。

截至 2024 年,

doctest.testmod
没有其他选择 直接退出并显示错误代码。 像
doctest.testmod(exit_on_error=1)
这样的东西会很好......

© www.soinside.com 2019 - 2024. All rights reserved.