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)
7 ответов
Вы можете легко захватить стандартный вывод, просто временно перенаправив 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()
Этот пример Python 3 основан на примере paxdiablo. Оно использует unittest.mock
, Он использует многократно используемый вспомогательный метод для создания утверждения.
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')
Универсальный TestStdout
класс, возможно mixin, в принципе может быть получен из вышеупомянутого.
Если вам случится использовать pytest
, имеет встроенную запись захвата. Пример (pytest
тесты):
def eggs():
print('eggs')
def test_spam(capsys):
eggs()
captured = capsys.readouterr()
assert captured.out == 'eggs\n'
Вы также можете использовать его с unittest
тестовые классы, хотя вам необходимо вставить объект фикстуры в тестовый класс, например, с помощью autouse fixture:
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)
Проверьте Доступ к захваченному выводу из тестовой функции для получения дополнительной информации.
Вы также можете использовать макет пакета, как показано ниже, который является примером из https://realpython.com/lessons/mocking-print-unit-tests.
from mock import patch
def greet(name):
print('Hello ', name)
@patch('builtins.print')
def test_greet(mock_print):
# The actual test
greet('John')
mock_print.assert_called_with('Hello ', 'John')
greet('Eric')
mock_print.assert_called_with('Hello ', 'Eric')
Ответ на @Acumenus говорит:
Он также использует многоразовый вспомогательный метод assert_stdout, хотя этот вспомогательный метод специфичен для тестируемой функции.
жирная часть кажется большим недостатком, поэтому я бы сделал следующее:
class TestCase(unittest.TestCase):
def assertStdout(self, expected_output):
return _AssertStdoutContext(self, expected_output)
class _AssertStdoutContext:
def __init__(self, testcase, expected):
self.testcase = testcase
self.expected = expected
self.captured = io.StringIO()
def __enter__(self):
sys.stdout = self.captured
return self
def __exit__(self, exc_type, exc_value, tb):
sys.stdout = sys.__stdout__
captured = self.captured.getvalue()
self.testcase.assertEqual(captured, self.expected)
это позволяет гораздо лучше и удобнее использовать повторно:
def test_print(self):
with self.assertStdout("test\n"):
print("test")
с помощью простого диспетчера контекста. (Также может быть желательно добавить
"\n"
к
expected_output
поскольку
print()
по умолчанию добавляет новую строку.)
Вы также можете получить стандартный вывод метода, используяcontextlib.redirect_stdout
:
import unittest
from contextlib import redirect_stdout
from io import StringIO
class TestMyStuff(unittest.TestCase):
# ...
def test_stdout(self):
with redirect_stdout(StringIO()) as sout:
my_command_that_prints_to_stdout()
# the stream replacing `stdout` is available outside the `with`
# you may wish to strip the trailing newline
retval = sout.getvalue().rstrip('\n')
# test the string captured from `stdout`
self.assertEqual(retval, "whatever_retval_should_be")
Дает вам локальное решение. Также можно зафиксировать стандартную ошибку, используяcontextlib.redirect_stderr()
.
Другой вариант опирается наlogging
модуль, а неprint()
. Этот модуль также предлагает, когда использоватьprint
в документации :
Отображение вывода консоли для обычного использования сценария или программы командной строки
PyTest имеет встроенную поддержку тестирования сообщений журнала.