简体   繁体   中英

Pytest capture stdout of a certain test

Is there a way get the Captured stdout call just for a specific test without failing the test?

So lets say I have 10 tests, plus a test_summary. test_summary really just prints some kind of summary/statistics of the tests, but in order for me to get that output/printout, I have to currently fail that test intentionally. Of course this test_summary run last using pytest-ordering. But is there a better way to get that results without failing the test? Or should it not be in a test, but more in the conftest.py or something? Please advice on the best practice and how I can get this summary/results (basically a printout from a specific script I wrote)

First, to answer your exact question:

Is there a way get the Captured stdout call just for a specific test without failing the test?

You can add a custom section that mimics Captured stdout call and is printed on test success. The output captured in each test is stored in the related TestReport object and is accessed via report.capstdout . Example impl: add the following code to a conftest.py in your project or tests root directory:

import os

def pytest_terminal_summary(terminalreporter, exitstatus, config):
    # on failures, don't add "Captured stdout call" as pytest does that already
    # otherwise, the section "Captured stdout call" will be added twice
    if exitstatus > 0:
        return
    # get all reports
    reports = terminalreporter.getreports('')
    # combine captured stdout of reports for tests named `<smth>::test_summary`
    content = os.linesep.join(
        report.capstdout for report in reports
        if report.capstdout and report.nodeid.endswith("test_summary")
    )
    # add custom section that mimics pytest's one
    if content:
        terminalreporter.ensure_newline()
        terminalreporter.section(
            'Captured stdout call',
            sep='-',
            blue=True,
            bold=True,
        )
        terminalreporter.line(content)

This will add a custom section Captured stdout call that will only print the output captured for the test whose ID ends with test_summary (if you have multiple test functions named test_summary , extend the check). To distinct both sections, the custom one has a blue header; if you want it to match the original, remove color setting via blue=True arg.


Now, to address your actual problem:

test_summary really just prints some kind of summary/statistics of the tests

Using a test for custom reporting smells a lot like a workaround to me; why not collect the data in the tests and add a custom section printing that data afterwards? To collect the data, you can eg use the record_property fixture:

def test_foo(record_property):
    # records a key-value pair
    record_property("hello", "world")


def test_bar(record_property):
    record_property("spam", "eggs")

To collect and output the custom properties recorded, slightly alter the above hookimpl. The data stored via record_property is accessible via report.user_properties :

import os


def pytest_terminal_summary(terminalreporter, exitstatus, config):
    reports = terminalreporter.getreports('')
    content = os.linesep.join(
        f'{key}: {value}' for report in reports
        for key, value in report.user_properties
    )
    if content:
        terminalreporter.ensure_newline()
        terminalreporter.section(
            'My custom summary',
            sep='-',
            blue=True,
            bold=True
        )
        terminalreporter.line(content)

Running the above tests now yields:

$ pytest test_spam.py 
=============================== test session starts ================================
platform linux -- Python 3.9.0, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
rootdir: /home/oleg.hoefling/projects/private/stackoverflow/so-64812992
plugins: metadata-1.10.0, json-report-1.2.4, cov-2.10.1, forked-1.3.0, xdist-2.1.0
collected 2 items                                                                  

test_spam.py ..                                                              [100%]

-------------------------------- My custom summary ---------------------------------
hello: world
spam: eggs
================================ 2 passed in 0.01s =================================

You can use the standard pytest terminal reporter.

def test_a(request):
    reporter = request.config.pluginmanager.getplugin("terminalreporter")
    reporter.write_line("Hello", yellow=True)
    reporter.write_line("World", red=True)

The reporter is the standard plugin available for all pytest versions that I can recall. It is not documented unfortunately while the API is pretty stable.

You can find TerminalReporter class on pytest's github: https://github.com/pytest-dev/pytest/blob/master/src/_pytest/terminal.py

The most useful for you methods are probably ensure_line() , write() , flush() and the absolute champion -- write_line() that just do all work.

Available styles could be found at https://github.com/pytest-dev/pytest/blob/master/src/_pytest/_io/terminalwriter.py

_esctable = dict(
    black=30,
    red=31,
    green=32,
    yellow=33,
    blue=34,
    purple=35,
    cyan=36,
    white=37,
    Black=40,
    Red=41,
    Green=42,
    Yellow=43,
    Blue=44,
    Purple=45,
    Cyan=46,
    White=47,
    bold=1,
    light=2,
    blink=5,
    invert=7,
)

Lowercased styles are for the foreground, capitalized are for background.

For example, the yellow on the green text should be printed as

reporter.write_line("Text", yellow=True, Green=True)

I hope this instruction can help.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM