【问题标题】:Mock Python's built in print function模拟 Python 的内置打印功能
【发布时间】:2012-10-11 12:33:59
【问题描述】:

我试过了

from mock import Mock
import __builtin__

__builtin__.print = Mock()

但这会引发语法错误。我也试过这样修补它

@patch('__builtin__.print')
def test_something_that_performs_lots_of_prints(self, mock_print):

    # assert stuff

有没有办法做到这一点?

【问题讨论】:

  • 您使用的是哪个版本的 Python?
  • Python 2.7.3,不过如果在 2.7.x 下不可能的话,很想知道如何在 3 下完成它
  • 是的,模拟 sys.stdout 可能是在测试情况下执行此操作的最佳方法。

标签: python unit-testing mocking python-2.x


【解决方案1】:

我知道已经有一个公认的答案,但是对于这个问题有一个更简单的解决方案 - 在 python 2.x 中模拟打印。答案在模拟库教程中:http://www.voidspace.org.uk/python/mock/patch.html 它是:

>>> from StringIO import StringIO
>>> def foo():
...     print 'Something'
...
>>> @patch('sys.stdout', new_callable=StringIO)
... def test(mock_stdout):
...     foo()
...     assert mock_stdout.getvalue() == 'Something\n'
...
>>> test()

当然你也可以使用下面的断言:

self.assertEqual("Something\n", mock_stdout.getvalue())

我已经在我的单元测试中检查了这个解决方案,它按预期工作。希望这可以帮助某人。干杯!

【讨论】:

  • 这不适用于 Python 2 中的 pytest(对于 cmets 中的格式,抱歉)。 "def test_status(mocker): mocker_print = mocker.patch("sys.stdout", new_callable=StringIO) > print("yes") E TypeError: unicode argument expected, got 'str'"
【解决方案2】:

这是一个更简单的 Python 3 解决方案——直接在内置的print 函数上使用unittest.mock 更容易,而不是摆弄sys.stdout

from unittest.mock import patch, call

@patch('builtins.print')
def test_print(mocked_print):
    print('foo')
    print()

    assert mocked_print.mock_calls == [call('foo'), call()]

【讨论】:

  • 这是对 Python 3 有意义的唯一方法。这样我们可以在模拟上执行断言,这是测试的主要优势。
  • 这应该是公认的答案。非常感谢
  • 感谢您的回答,这是我最终使用的。但是我注意到最好将补丁限制在我希望使用 print 的上下文(它被导入的模块)中,否则我将修补 pytest、pdb 等使用的内置 - 例如这阻止了我闯入测试代码。所以假设调用是在模块myapp.mymod中,我会修补myapp.mymod.print
【解决方案3】:

print 是 python 2.x 中的关键字,将其用作属性会引发 SyntaxError。您可以通过在文件开头使用from __future__ import print_function 来避免这种情况。

注意:您不能简单地使用setattr,因为除非print 语句被禁用,否则您修改的打印函数不会被调用。

编辑:您还需要在要使用修改后的print 函数的每个文件中添加from __future__ import print_function,否则它将被print 语句屏蔽。

【讨论】:

  • 所以我可以setattr(__builtin__, 'print', Mock()) 然后以某种方式禁用打印语句?还是您的意思是通过导入 Python 3 打印功能来禁用它?如果能够完全在测试端执行此操作而无需修改被测代码,那就太好了。
  • @aychedee 在所有需要使用你修改的print函数的源文件中,你需要通过导入Python 3 print函数来禁用它。以 lqc 的方式使用的 setattr 将不起作用,因为它被 print 语句所掩盖。
  • 好的,干杯,谢谢小猫。这样可行。所以答案是,这只能通过使用 Python 3 打印函数来实现,没有它就行不通。
  • 能否提供一个完整的例子来测试对print的实际调用?
【解决方案4】:

如果您想坚持使用 2.x 中的 print 语句而不是 2.x 中的 print() 函数,您可以模拟您的 sys.stdout

写一个虚拟的“文件”,大概是这样:

class Writable(object):
    """Class which has the capability to replace stdout."""
    newwrite = None
    def __init__(self, oldstdout, newwrite=None):
        self.oldstdout = oldstdout
        if newwrite is not None:
            self.newwrite = newwrite
    def write(self, data):
        self.newwrite(self.oldstdout, data)
    @classmethod
    def subclass(cls, writefunc):
        newcls = type('', (cls,),
            dict(write=lambda self, data: writefunc(self.oldstdout, data)
        return newcls

这个类期望与一个获取打印数据的写入函数相结合。这个写入函数应该有 2 个参数:第一个带有“旧标准输出”的最后用于打印,另一个用于数据。

我们来

def mywrite(sink, data):
    sink.write(data.encode("hex"))

为此。

现在你可以做

import sys
sys.stdout = Writable(sys.stdout, mywrite)

或者你可以这样做

@Writable.subclass
def mywritable(sink, data)
    sink.write(data.encode("hex"))

sys.stdout = mywritable(sys.stdout)

第二个版本有点棘手:它在装饰器函数的帮助下创建Writable 的子类,该函数将给定函数转换为创建的新类的方法,并放入给定函数所在的名称中来自。

之后,您就有了一个新类,可以使用“旧标准输出”作为参数进行实例化,之后可以替换 sys.stdout

【讨论】:

    【解决方案5】:

    我的版本。

    在被测程序中(例如:pp.py):

    from __future__ import print_function
    
    def my_func():
        print('hello')
    

    在测试程序中:

    def test_print(self):
        from pp import my_func
        from mock import call
        with mock.patch('__builtin__.print') as mock_print:
           my_func()
           mock_print.assert_has_calls(
                [
                    call('hello')
                ]
            )
    

    【讨论】:

      【解决方案6】:
      from unittest.mock import patch
      
      
      def greet():
          print("Hello World")
      
      
      @patch('builtins.print')
      def test_greet(mock_print):
          greet()
          mock_print.assert_called_with("Hello World!")
      

      【讨论】:

      【解决方案7】:
      import mock
      import sys
      
      mock_stdout = mock.Mock()
      sys.stdout = mock_stdout
      print 'Hello!'
      sys.stdout = sys.__stdout__
      
      print mock_stdout.mock_calls
      [call.write('Hello!'), call.write('\n')]
      

      【讨论】:

      • 如果您解释一下您在这里做了什么以及为什么问题中的代码不起作用,将会很有帮助。
      • 我们正在通过我们的mock对象切换sys.stdout,当我们打印一些文本时,可能会在mock_stdout调用中找到它。最后我们将 sys.stdout 恢复到原始状态。
      【解决方案8】:

      这是@KC 答案的 v3 版本。

      我不想模拟打印,因为我特别想查看整个输出,而不是检查单个调用,因此 StringIO 对我来说更有意义。

      from io import StringIO
      from unittest.mock import patch
      
      def foo():
          print ('Something')
      
      def test():
          with patch('sys.stdout', new_callable=StringIO) as buffer:
              foo()
          fake_stdout = buffer.getvalue()
      
          #print() is back!
          print(f"fake_stdout:{fake_stdout}")
          assert fake_stdout == 'Something\n'
      
      test()
      

      ???警告:

      在补丁期间,使用pdb.set_trace() 模拟标准输出效果不佳。我注释掉了with...,添加了if True: 以保留缩进,调试了我的脚本并在修复错误后放回了批处理。

          #with patch('sys.stdout', new_callable=StringIO) as buffer:
          if True:
              foo()
          ...
      

      【讨论】:

        【解决方案9】:

        首先,模块名为__builtins__,您不需要导入它。

        现在,在 Python 2 中,print 是一个关键字,因此您不能直接将其用作属性名称。您可以使用setattr/getattr 来解决它:

        getattr(__builtins__, "print")
        

        另一种选择是使用from __future__ import print_function,它将 Python 将模块解析为 Python 3 语法的方式。

        【讨论】:

        • __builtin__ == __builtins__ >>> True 在 2.7.3 中。那么如果不使用 Python 3 的打印功能,答案是否定的?
        【解决方案10】:

        正如 lcq 所说,print 是一个关键字。所以,想想如果你真的成功地在 Python 2.7.3 下修补/模拟打印,这意味着什么。你会有这样的代码:

        print "Hi."
        

        变成:

        <MagicMock id='49489360'> "Hi."
        

        MagicMock 对象无法通过这种方式访问​​,因此会出现语法错误。

        所以...是的。您只能模拟 Python3 打印 函数 或 sys.stdout。

        【讨论】:

          【解决方案11】:

          这个 Python 3 示例建立在 Krzysztof 的 Python 2 答案之上。它使用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')
          

          【讨论】:

            猜你喜欢
            • 2023-03-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-07-05
            • 2011-11-19
            • 1970-01-01
            相关资源
            最近更新 更多