Python: 为控制台打印编写 unittest

函数 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)
66757 次浏览

您可以通过临时将 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答案使用 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方法的。

一个通用的 TestStdout类,可能是 Mixin,原则上可以从上面派生出来。

对于那些使用 Python ≥3.4的用户,也存在 contextlib.redirect_stdout,但它似乎不比 unittest.mock.patch有什么好处。

如果你碰巧使用 pytest,它有内置的输出捕获。示例(pytest风格的测试) :

def eggs():
print('eggs')




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

您也可以将它与 unittest测试类一起使用,尽管您需要通过 fixture 对象传递到测试类,例如通过 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)

查看 访问从测试函数捕获的输出了解更多信息。

@ Acumenus 的 回答表示:

它还使用了一个可重用的助手方法 asser_ stdout,尽管这个助手是特定于被测试的函数的。

大胆的部分似乎是一个很大的缺点,因此我会采取以下措施:

# 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")

通过使用一个直接的上下文管理器。(在 expected_output后面附加 "\n"也是可取的,因为 print()在默认情况下添加了一个新行。请看下一个例子...)

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

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

现在是可能的。

另一个变体是倾向于 logging模块,而不是 print()。该模块还对何时使用 print 在文件中提出了建议:

显示命令行脚本或程序通常使用的控制台输出

PyTest 具有用于测试日志记录消息的 内置支持