【问题标题】:How to save pytest's results/logs to a file?如何将 pytest 的结果/日志保存到文件中?
【发布时间】:2015-08-03 17:45:37
【问题描述】:

我在尝试将 pytest 显示的所有结果保存到文件(txt、日志、无关紧要)时遇到问题。在下面的测试示例中,我想将控制台中显示的内容捕获到某种文本/日志文件中:

import pytest
import os

def test_func1():
    assert True


def test_func2():
    assert 0 == 1

if __name__ == '__main__':

    pytest.main(args=['-sv', os.path.abspath(__file__)])

我想保存到文本文件的控制台输出:

test-mbp:hi_world ua$ python test_out.py
================================================= test session starts =================================================
platform darwin -- Python 2.7.6 -- py-1.4.28 -- pytest-2.7.1 -- /usr/bin/python
rootdir: /Users/tester/PycharmProjects/hi_world, inifile: 
plugins: capturelog
collected 2 items 

test_out.py::test_func1 PASSED
test_out.py::test_func2 FAILED

====================================================== FAILURES =======================================================
_____________________________________________________ test_func2 ______________________________________________________

    def test_func2():
>       assert 0 == 1
E       assert 0 == 1

test_out.py:9: AssertionError
========================================= 1 failed, 1 passed in 0.01 seconds ==========================================
test-mbp:hi_world ua$ 

【问题讨论】:

  • 目前还没有确切的答案

标签: python logging pytest


【解决方案1】:

看来你所有的测试输出都在stdout,所以你只需要在那里“重定向”你的python调用的输出:

python test_out.py >myoutput.log

您还可以将输出“发送”到多个位置。例如,您可能希望登录到该文件,同时还希望在控制台上查看输出。上面的例子就变成了:

python test_out.py | tee myoutput.log

【讨论】:

  • 好像tee 包含在 Windows 10 中。
  • 这是基于 OP 要求的正确答案,但另一个有用的答案是 pytest --junitxml=./output.xml。这会输出一个 junit xml 文件,该文件可以在 junit 查看器中打开,例如 xunit-viewer
  • 在我的 pytest 构建文件时是否可以显示某种进度条?目前,当我做同样的事情时,需要一些时间来完成测试并将其写入文件。因此,某种进度计数器将有助于检查程序是否正在运行。
【解决方案2】:

根据 Bruno Oliveira 的建议,我从 pastebin 得到这个:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Pytest Plugin that save failure or test session information to a file pass as a command line argument to pytest.

It put in a file exactly what pytest return to the stdout.

To use it :
Put this file in the root of tests/ edit your conftest and insert in the top of the file :

    pytest_plugins = 'pytest_session_to_file'

Then you can launch your test with the new option --session_to_file= like this :

    py.test --session_to_file=FILENAME
Or :
    py.test -p pytest_session_to_file --session_to_file=FILENAME


Inspire by _pytest.pastebin
Ref: https://github.com/pytest-dev/pytest/blob/master/_pytest/pastebin.py

Version : 0.1
Date : 30 sept. 2015 11:25
Copyright (C) 2015 Richard Vézina <ml.richard.vezinar @ gmail.com>
Licence : Public Domain
"""

import pytest
import sys
import tempfile


def pytest_addoption(parser):
    group = parser.getgroup("terminal reporting")
    group._addoption('--session_to_file', action='store', metavar='path', default='pytest_session.txt',
                     help="Save to file the pytest session information")


@pytest.hookimpl(trylast=True)
def pytest_configure(config):
    tr = config.pluginmanager.getplugin('terminalreporter')
    # if no terminal reporter plugin is present, nothing we can do here;
    # this can happen when this function executes in a slave node
    # when using pytest-xdist, for example
    if tr is not None:
        config._pytestsessionfile = tempfile.TemporaryFile('w+')
        oldwrite = tr._tw.write

        def tee_write(s, **kwargs):
            oldwrite(s, **kwargs)
            config._pytestsessionfile.write(str(s))
        tr._tw.write = tee_write


def pytest_unconfigure(config):
    if hasattr(config, '_pytestsessionfile'):
        # get terminal contents and delete file
        config._pytestsessionfile.seek(0)
        sessionlog = config._pytestsessionfile.read()
        config._pytestsessionfile.close()
        del config._pytestsessionfile
        # undo our patching in the terminal reporter
        tr = config.pluginmanager.getplugin('terminalreporter')
        del tr._tw.__dict__['write']
        # write summary  
        create_new_file(config=config, contents=sessionlog)


def create_new_file(config, contents):
    """
    Creates a new file with pytest session contents.
    :contents: paste contents
    :returns: url to the pasted contents
    """
    # import _pytest.config
    # path = _pytest.config.option.session_to_file
    # path = 'pytest_session.txt'
    path = config.option.session_to_file
    with open(path, 'w') as f:
        f.writelines(contents)


def pytest_terminal_summary(terminalreporter):
    import _pytest.config
    tr = terminalreporter
    if 'failed' in tr.stats:
        for rep in terminalreporter.stats.get('failed'):
            try:
                msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc
            except AttributeError:
                msg = tr._getfailureheadline(rep)
            tw = _pytest.config.create_terminal_writer(terminalreporter.config, stringio=True)
            rep.toterminal(tw)
            s = tw.stringio.getvalue()
            assert len(s)
            create_new_file(config=_pytest.config, contents=s)

【讨论】:

  • 其实我在打包这个pytest插件的过程中,终于找到了第三方插件列表(pytest.org/latest/plugins_index/index.html),不知道是不是和pytest不一样-capturelog 或 pytest-catchlog,插件的输出肯定不一样,尽管它可能提供相同的信息。如果知道这些插件的人可以确认......如果是这样,我最好添加从这些插件获得相同输出的能力,然后是上面的那个......
  • 这里终于有一个可以工作的 pypi 包了:pypi.python.org/pypi/pytest-session2file/0.1.5
  • 如果您调用 pytest.main(),这是否也有效?我正在尝试保存 pytest 测试的输出,令人惊讶的是,这里的几乎所有答案都没有提到一次 pytest.main() 表单。谢谢!
  • 我不明白为什么不...但是我没有尝试,因为我测试的东西我没有成功使用 pytest.main()... 所以我创建了自己的测试午餐脚本来减少样板...注意,我从来没有时间更新 pypi 包,但在 github repo 中有针对 python 3 的修复...
【解决方案3】:

pastebin 内部插件正是这样做的,但将输出直接发送到 bpaste.net。您可以查看插件实现以了解如何根据需要重用它。

【讨论】:

    【解决方案4】:

    这是一个夹具,以便您能够做到这一点,我使用了 pytest 缓存功能,以便利用可以传递给多个测试文件的夹具,包括分布式测试(xdist),以便能够收集和打印测试结果。

    conftest.py:

    from _pytest.cacheprovider import Cache
    from collections import defaultdict
    
    import _pytest.cacheprovider
    import pytest
    
    @pytest.hookimpl(tryfirst=True)
    def pytest_configure(config):
        config.cache = Cache(config)
        config.cache.set('record_s', defaultdict(list))
    
    @pytest.fixture(autouse=True)
    def record(request):
        cache = request.config.cache
        record_s = cache.get('record_s', {})
        testname = request.node.name
        # Tried to avoid the initialization, but it throws errors.
        record_s[testname] = []
        yield record_s[testname]
        cache.set('record_s', record_s)
    
    @pytest.hookimpl(trylast=True)
    def pytest_unconfigure(config):
        print("====================================================================\n")
        print("\t\tTerminal Test Report Summary: \n")
        print("====================================================================\n")
        r_cache = config.cache.get('record_s',{})
        print str(r_cache)
    

    用途:

    def test_foo(record):
        record.append(('PASS', "reason", { "some": "other_stuff" }))
    

    输出:

    ====================================================================
    
            Terminal Test Report Summary:
    
    ====================================================================
    
    {u'test_foo': [[u'PASS',u'reason', { u'some': u'other_stuff' } ]]}
    

    【讨论】:

      猜你喜欢
      • 2018-06-19
      • 1970-01-01
      • 1970-01-01
      • 2020-02-15
      • 1970-01-01
      • 2023-01-29
      • 2021-03-19
      • 2017-09-25
      • 1970-01-01
      相关资源
      最近更新 更多