Python: Как перехватить стандартный вывод stdout/stderr в переменной?

Как записать stdout/stderr юнит-теста в переменную? Мне нужно захватить весь выходной результат следующего модульного теста и отправить его в SQS. Я попробовал это:

import unittest, io
from contextlib import redirect_stdout, redirect_stderr


class LogProcessorTests(unittest.TestCase):
    def setUp(self):
        self.var = 'this value'

    def test_var_value(self):
        with io.StringIO() as buf, redirect_stderr(buf):
            print('Running LogProcessor tests...')
            print('Inside test_var_value')
            self.assertEqual(self.var, 'that value')
            print('-----------------------')
            print(buf.getvalue())

Однако это не работает, и следующий вывод появляется только в stdout/stderr.

Testing started at 20:32 ...
/Users/myuser/Documents/virtualenvs/app-venv3/bin/python3 "/Applications/PyCharm CE.app/Contents/helpers/pycharm/_jb_unittest_runner.py" --path /Users/myuser/Documents/projects/application/LogProcessor/tests/test_processor_tests.py
Launching unittests with arguments python -m unittest /Users/myuser/Documents/projects/application/LogProcessor/tests/test_processor_tests.py in /Users/myuser/Documents/projects/application/LogProcessor/tests
Running LogProcessor tests...
Inside test_var_value

that value != this value

Expected :this value
Actual   :that value
<Click to see difference>

Traceback (most recent call last):
  File "/Applications/PyCharm CE.app/Contents/helpers/pycharm/teamcity/diff_tools.py", line 32, in _patched_equals
    old(self, first, second, msg)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 839, in assertEqual
    assertion_func(first, second, msg=msg)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 1220, in assertMultiLineEqual
    self.fail(self._formatMessage(msg, standardMsg))
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 680, in fail
    raise self.failureException(msg)
AssertionError: 'this value' != 'that value'
- this value
?   ^^
+ that value
?   ^^

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 59, in testPartExecutor
    yield
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 615, in run
    testMethod()
  File "/Users/myuser/Documents/projects/application/LogProcessor/tests/test_processor_tests.py", line 15, in test_var_value
    self.assertEqual(self.var, 'that value')



Ran 1 test in 0.004s

FAILED (failures=1)

Process finished with exit code 1

Assertion failed

Assertion failed

Любая идея? Пожалуйста, дайте мне знать, если вам нужна дополнительная информация.

2 ответа

Решение

Если вы вручную создаете тестовый запуск (например, unittest.TextTestRunner), вы можете указать (файл) поток, в который он пишет. По умолчанию это sys.stderr, но вы можете использовать вместо StringIO. Это будет захватывать результаты самого unittest. Вывод ваших собственных операторов печати не будет записан, но вы можете использовать redirect_stdout контекстный менеджер для этого, использующий тот же объект StringIO.

Обратите внимание, что я бы рекомендовал избегать использования операторов print, так как они будут мешать выводу инфраструктуры unittest (ваш тестовый вывод сломает выходные строки инфраструктуры unittest), и перенаправление stdout/stderr - это хакерство потоки. Лучшим решением было бы использовать logging модуль вместо. Затем вы можете добавить обработчик регистрации, который записывает все сообщения журнала в StringIO для дальнейшей обработки (в вашем случае: отправка в SQS).

Ниже приведен пример кода на основе вашего кода с использованием print-операторов.

#!/usr/bin/env python3

import contextlib
import io
import unittest


class LogProcessorTests(unittest.TestCase):

    def setUp(self):
        self.var = 'this value'

    def test_var_value(self):
        print('Running LogProcessor tests...')
        print('Inside test_var_value')
        self.assertEqual(self.var, 'that value')
        print('-----------------------')


if __name__ == '__main__':
    # find all tests in this module
    import __main__
    suite = unittest.TestLoader().loadTestsFromModule(__main__)
    with io.StringIO() as buf:
        # run the tests
        with contextlib.redirect_stdout(buf):
            unittest.TextTestRunner(stream=buf).run(suite)
        # process (in this case: print) the results
        print('*** CAPTURED TEXT***:\n%s' % buf.getvalue())

Это печатает:

*** CAPTURED TEXT***:
Running LogProcessor tests...
Inside test_var_value
F
======================================================================
FAIL: test_var_value (__main__.LogProcessorTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 16, in test_var_value
    self.assertEqual(self.var, 'that value')
AssertionError: 'this value' != 'that value'
- this value
?   ^^
+ that value
?   ^^


----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

Это подтверждает, что все выходные данные (из среды unittest и самого тестового примера) были захвачены в объекте StringIO.

Основываясь на документации contextlib.redirect_stdout, вот как вы перенаправляетеstderr или stdout:

import io
import contextlib

f = io.StringIO()
with contextlib.redirect_stderr(f):
    parser = target.parse_args([])
self.assertTrue("error: one of the arguments -p/--propagate -cu/--cleanup is required" in f.getvalue())

Вы также можете комбинировать это с другим менеджером контекста (например, assertRaises) нравится:

f = io.StringIO()
with self.assertRaises(SystemExit) as cm, contextlib.redirect_stderr(f):
    parser = target.parse_args([])
self.assertEqual(cm.exception.code, 2)
self.assertTrue("error: one of the arguments -p/--propagate -cu/--cleanup is required" in f.getvalue())

Честно говоря, самый простой способ - это, вероятно, перенаправить вывод на уровне операционной системы - запустить тест из командной строки и> в файл.

Если вы используете систему сборки для их выполнения, тогда система сборки должна захватывать вывод для вас, и вы можете извлечь вывод из его артефактов сборки.

Другие вопросы по тегам