Python: 为控制台打印编写单元测试

Python: Write unittest for console print

函数 foo 打印到控制台。我想测试控制台打印。我怎样才能在 python 中做到这一点?

需要测试此功能,没有 return 声明:

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)

您可以通过暂时将 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。 Python3略有不同:

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 回答使用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 arg 由 unittest.mock.patch 装饰器自动传递给 assert_stdout 方法。

一个通用的TestStdoutclass,可能是mixin,原则上可以从上面推导出来。

对于那些使用 Python ≥3.4, contextlib.redirect_stdout also exists, but it seems to serve no benefit over unittest.mock.patch.

如果您碰巧使用 pytest,它具有内置的输出捕获功能。示例(pytest 式测试):

def eggs():
    print('eggs')


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

您也可以将它与 unittest 测试 classes 一起使用,尽管您需要将夹具对象传递到测试中 class,例如通过自动夹具:

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)

查看 Accessing captured output from a test function 了解更多信息。

你也可以使用如下所示的mock包,示例来自 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 的 说:

It also uses a reusable helper method assert_stdout, although this helper is specific to the function being tested.

粗体部分似乎是一个很大的缺点,因此我会改为执行以下操作:

# extend unittest.TestCase with new functionality
class TestCase(unittest.TestCase):

    def assertStdout(self, expected_output):
        return _AssertStdoutContext(self, expected_output)

    # as a bonus, this syntactical sugar becomes possible:
    def assertPrints(self, *expected_output):
        expected_output = "\n".join(expected_output) + "\n"
        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)

这允许更好、更可重用:

# in a specific test case, the new method(s) can be used
class TestPrint(TestCase):

    def test_print1(self):
        with self.assertStdout("test\n"):
            print("test")

通过使用直接的上下文管理器。 (可能还需要将 "\n" 附加到 expected_output,因为 print() 默认添加换行符。请参见下一个示例...)

此外,这个非常好的变体(打印任意数量!)

    def test_print2(self):
        with self.assertPrints("test1", "test2"):
            print("test1")
            print("test2")

现在可以了。