web-dev-qa-db-ja.com

pytestの結果/ログをファイルに保存する方法は?

Pytestから表示されたすべての結果をファイルに保存しようとして問題が発生しました(txt、log、関係ありません)。以下のテスト例では、コンソールに表示されているものをある種のテキスト/ログファイルにキャプチャしたいと思います。

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$ 
11
nonbot

すべてのテスト出力が実行されているようですstdoutなので、python呼び出しの出力をそこに「リダイレクト」する必要があります:

python test_out.py >myoutput.log

出力を複数の場所に「ティー」することもできます。たとえば、ファイルにログを記録しながら、コンソールに出力を表示したい場合があります。上記の例は次のようになります。

python test_out.py | tee myoutput.log
16
Micah Elliott

これを実行できるようにするためのフィクスチャを次に示します。分散テスト(xdist)を含む複数のテストファイルに渡すことができるフィクスチャを活用するために、pytestキャッシュ機能を使用しました。テスト結果を収集して印刷します。

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' } ]]}
0

Pastebin 内部プラグインはまさにそれを行いますが、出力をbpaste.netに直接送信します。プラグインの実装を見て、ニーズに合わせて再利用する方法を理解できます。

0
Bruno Oliveira

私はこれを Pastebin から導き出します。BrunoOliveiraが示唆しています:

#!/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)
0
Richard