使用docstrings列出py.test中的测试

时间:2015-03-06 12:34:52

标签: python unit-testing pytest

这是一个简单的测试文件:

# test_single.py
def test_addition():
    "Two plus two is still four"
    assert 2 + 2 == 4

def test_addition2():
    "One plus one is still two"
    assert 1 + 1 == 2

py.test中的默认输出类似于

$ py.test test_single.py -v
[...]
test_single.py::test_addition PASSED
test_single.py::test_addition2 PASSED

我想

Two plus two is still four PASSED
One plus one is still two PASSED

即。使用docstrings作为测试的描述。

我尝试在conftest.py文件中使用自定义:

import pytest

@pytest.mark.tryfirst
def pytest_runtest_makereport(item, call, __multicall__):
    # execute all other hooks to obtain the report object
    rep = __multicall__.execute()
    if rep.when == "call":
        extra = item._obj.__doc__.strip()
        rep.nodeid =  extra
    return rep

即接近,但它会在每一行重复文件名:

$ py.test test_single.py
======================================================================================== test session starts =========================================================================================
platform darwin -- Python 2.7.7 -- py-1.4.26 -- pytest-2.6.4
plugins: greendots, osxnotify, pycharm
collected 2 items

test_single.py
And two plus two is still four .
test_single.py
And one plus one is still two .

====================================================================================== 2 passed in 0.11 seconds ======================================================================================

如何在输出中避免使用test_single.py的行,或者只打印一次?

查看py.test的来源及其部分插件没有帮助。

我知道pytest-spec插件,但它使用函数的名称作为描述。我不想写def test_two_plus_two_is_four()

5 个答案:

答案 0 :(得分:17)

将我的评论扩展到@ michael-wan的答案:获得类似specplugin的内容conftest.py

def pytest_itemcollected(item):
    par = item.parent.obj
    node = item.obj
    pref = par.__doc__.strip() if par.__doc__ else par.__class__.__name__
    suf = node.__doc__.strip() if node.__doc__ else node.__name__
    if pref or suf:
        item._nodeid = ' '.join((pref, suf))

pytest输出
class TestSomething:
"""Something"""

def test_ok(self):
    """should be ok"""
    pass

看起来像

py.test screen grab

如果省略docstrings将使用class / func名称。

答案 1 :(得分:5)

我错过了rspec in ruby for python。因此,基于插件pytest-testdox.,我编写了类似的文档字符串作为报告消息。你可以查看his answer on splitting arguments into tokenspytest-pspec

答案 2 :(得分:4)

对于(我认为)开箱即用的插件,请查看pytest-testdox

它提供了每个测试函数名称的友好格式化列表,其中test_被剥离,下划线被替换为空格,因此测试名称是可读的。它还按测试文件分解了部分。

这是输出的样子:

enter image description here

答案 3 :(得分:2)

@Matthias Berth,您可以尝试使用pytest_itemcollected

def pytest_itemcollected(item):
""" we just collected a test item. """
    item.setNodeid('' if item._obj.__doc__ is None else item._obj.__doc__.strip() )

并修改pydir / lib / site-packages / pytest-2.9.1-py2.7.egg / _pytest / unittest.py 将以下函数添加到TestCaseFunction类

def setNodeid(self, value):
    self._nodeid = value

,结果将是:

platform win32 -- Python 2.7.10, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- D:\Python27\python.exe
cachedir: .cache
rootdir: E:\workspace\satp2\atest\testcase\Search\grp_sp, inifile:
plugins: html-1.8.0, pep8-1.0.6
collecting 0 itemsNone
collected 2 items
Two plus two is still four <- sut_amap3.py PASSED
One plus one is still two <- sut_amap3.py PASSED

enter image description here 当你使用pytest-html的时候 您可以使用您创建的pytest_runtest_makereport函数,它将生成具有您自定义名称的报告。 希望这有帮助。

答案 4 :(得分:0)

我想做同样的事情,但是要用一种更简单的方式,最好不要使用外部插件来做超出需要的事情,并且还要避免更改nodeid,因为它可能会破坏其他东西

我想出了以下解决方案:

test_one.py

import logging

logger = logging.getLogger(__name__)

def test_one():
    """ The First test does something """
    logger.info("One")

def test_two():
    """ Now this Second test tests other things """
    logger.info("Two")

def test_third():
    """ Third test is basically checking crazy stuff """
    logger.info("Three")

conftest.py

import pytest
import inspect

@pytest.mark.trylast
def pytest_configure(config):
    terminal_reporter = config.pluginmanager.getplugin('terminalreporter')
    config.pluginmanager.register(TestDescriptionPlugin(terminal_reporter), 'testdescription')

class TestDescriptionPlugin:

    def __init__(self, terminal_reporter):
        self.terminal_reporter = terminal_reporter
        self.desc = None

    def pytest_runtest_protocol(self, item):
        self.desc = inspect.getdoc(item.obj)

    @pytest.hookimpl(hookwrapper=True, tryfirst=True)
    def pytest_runtest_logstart(self, nodeid, location):
        if self.terminal_reporter.verbosity == 0:
            yield
        else:
            self.terminal_reporter.write('\n')
            yield
            if self.desc:
                    self.terminal_reporter.write(f'\n{self.desc} ')

运行--verbose

============================= test session starts =============================
platform win32 -- Python 3.8.2, pytest-5.4.1.dev62+g2d9dac95e, py-1.8.1, pluggy-0.13.1 -- C:\Users\Victor\PycharmProjects\pytest\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\Victor\PycharmProjects\pytest, inifile: tox.ini
collecting ... collected 3 items


test_one.py::test_one 
The First test does something  PASSED                                    [ 33%]

test_one.py::test_two 
Now this Second test tests other things  PASSED                          [ 66%]

test_one.py::test_third 
Third test is basically checking crazy stuff  PASSED                     [100%]

============================== 3 passed in 0.07s ==============================

运行--log-cli-level=INFO

============================= test session starts =============================
platform win32 -- Python 3.8.2, pytest-5.4.1.dev62+g2d9dac95e, py-1.8.1, pluggy-0.13.1
rootdir: C:\Users\Victor\PycharmProjects\pytest, inifile: tox.ini
collected 3 items


test_one.py::test_one 
The First test does something  
-------------------------------- live log call --------------------------------
INFO     test_one:test_one.py:7 One
PASSED                                                                   [ 33%]

test_one.py::test_two 
Now this Second test tests other things  
-------------------------------- live log call --------------------------------
INFO     test_one:test_one.py:11 Two
PASSED                                                                   [ 66%]

test_one.py::test_third 
Third test is basically checking crazy stuff  
-------------------------------- live log call --------------------------------
INFO     test_one:test_one.py:15 Three
PASSED                                                                   [100%]

============================== 3 passed in 0.07s ==============================

conftest.py中的插件可能足够简单,任何人都可以根据自己的需求进行自定义。