【问题标题】:Python: Write unittest for console printPython:为控制台打印编写单元测试
【发布时间】:2016-02-19 10:55:06
【问题描述】:

函数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)

【问题讨论】:

  • duplicate stackoverflow.com/questions/12998908/… tl;dr 使用 future 将 print 转换为内置函数或在替换的标准输出文件上断言
  • 我不想嘲笑任何东西。事实上,我的实际 foo 需要大约 8 个参数,它返回一个 json。我也想测试一下。

标签: python python-2.7 unit-testing console python-unittest


【解决方案1】:

@Acumenus 的answer 说:

它还使用了一个可重用的辅助方法 assert_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")

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

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

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

现在可以了。

【讨论】:

  • 因为你的类被称为 TestCase 我假设你正在继承 unittest.TestCase 来扩展它,并且 def test_print(self) 是类 TestPrintClass(TestCase) 的一部分,其中 TestCase 是你的扩展实现.那是对的吗? ——也许在这里说明显而易见的事情,但这是在阅读代码时突然出现在我脑海中的东西
  • 绝对正确。抱歉,在示例中遗漏了 TestPrintClass。我会加进去的!
  • 我输入了这段代码,但收到错误AttributeError: '_io.TextIOWrapper' object has no attribute 'getvalue'
  • 奇怪。你用的是哪个python版本?文档甚至没有提到包含该方法的哪个版本:https://docs.python.org/3/library/io.html#io.StringIO.getvalue(它适用于其他方法)。
【解决方案2】:

您只需将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.8.6 中使用 import ioio.StringIO()。对于SocketIO,我得到了AttributeError: module 'io' has no attribute 'SocketIO'
  • @EnriqueRené:我不确定我是否理解您的评论。自从 2017 年添加了 Python3 部分以来,我在 Python3 位的答案中有io.StringIO()曾经没有提到过修订SocketIO
  • @paxdiablo 我的评论是关于上面的@Alex 评论,据我所知,我也可以使用SocketIO。我尝试了这个建议并得到了AttributeError
【解决方案3】:

您还可以使用如下所示的模拟包,这是来自的示例 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')

【讨论】:

  • 我喜欢这个,谢谢,效果很好。
【解决方案4】:

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

def eggs():
    print('eggs')


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

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

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 了解更多信息。

【讨论】:

    【解决方案5】:

    此 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 没有任何好处。

    【讨论】:

      猜你喜欢
      • 2021-11-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多