Python: написать юнит-тест для консольной печати

Функция foo печатает на консоли. Я хочу протестировать консольную печать. Как я могу достичь этого в python?

Необходимо протестировать эту функцию, не имеет оператора возврата:

def foo(inStr):
   print "hi"+inStr

Мой тест:

def test_foo():
    cmdProcess = subprocess.Popen(foo("test"), stdout=subprocess.PIPE)
    cmdOut = cmdProcess.communicate()[0]
    self.assertEquals("hitest", cmdOut)

Ответ 1

Вы можете легко захватить стандартный вывод, просто временно перенаправив sys.stdout на объект StringIO, следующим образом:

import StringIO
import sys

def foo(inStr):
    print "hi"+inStr

def test_foo():
    capturedOutput = StringIO.StringIO()          # Create StringIO object
    sys.stdout = capturedOutput                   #  and redirect stdout.
    foo('test')                                   # Call unchanged function.
    sys.stdout = sys.__stdout__                   # Reset redirect.
    print 'Captured', capturedOutput.getvalue()   # Now works as before.

test_foo()

Выход этой программы:

Captured hitest

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


Обратите внимание, что приведенный выше код для Python 2.7, как показывает вопрос. Python 3 немного отличается:

import io
import sys

def foo(inStr):
    print ("hi"+inStr)

def test_foo():
    capturedOutput = io.StringIO()                  # Create StringIO object
    sys.stdout = capturedOutput                     #  and redirect stdout.
    foo('test')                                     # Call function.
    sys.stdout = sys.__stdout__                     # Reset redirect.
    print ('Captured', capturedOutput.getvalue())   # Now works as before.

test_foo()

Ответ 2

Этот ответ Python 3 использует unittest.mock. Он также использует многократно используемый вспомогательный метод assert_stdout, хотя этот вспомогательный параметр специфичен для тестируемой функции.

import io
import unittest
import unittest.mock

from .solution import fizzbuzz


class TestFizzBuzz(unittest.TestCase):

    @unittest.mock.patch('sys.stdout', new_callable=io.StringIO)
    def assert_stdout(self, n, expected_output, mock_stdout):
        fizzbuzz(n)
        self.assertEqual(mock_stdout.getvalue(), expected_output)

    def test_only_numbers(self):
        self.assert_stdout(2, '1\n2\n')

Обратите внимание, что mock_stdout автоматически unittest.mock.patch декоратором assert_stdout метод assert_stdout.

TestStdout класс TestStdout, возможно, mixin, в принципе может быть получен из вышеупомянутого.

Для тех, кто использует Python ≥3.4, также существует contextlib.redirect_stdout, но, похоже, он не unittest.mock.patch никаких преимуществ по сравнению с unittest.mock.patch.

Ответ 3

Если вы используете pytest, он имеет встроенный захват вывода. Пример (pytest -style):

def eggs():
    print('eggs')


def test_spam(capsys):
    eggs()
    captured = capsys.readouterr()
    assert captured.out == 'eggs\n'

Вы также можете использовать его с тестовыми классами unittest, хотя вам необходимо вставить объект фикстуры в тестовый класс, например, через фиксацию autouse:

import unittest
import pytest


class TestSpam(unittest.TestCase):

    @pytest.fixture(autouse=True)
    def _pass_fixtures(self, capsys):
        self.capsys = capsys

    def test_eggs(self):
        eggs()
        captured = self.capsys.readouterr()
        self.assertEqual('eggs\n', captured.out)

Проверьте Доступ к захваченному выводу из тестовой функции для получения дополнительной информации.