如何创建使用unittest.mock.patch函数吞下sys.stdout的装饰器?

时间:2019-10-09 10:29:23

标签: stdout decorator mixins python-unittest.mock

我正在尝试为我的带有返回值和sys.stdout的单元测试测试函数创建一个Python Mixin。我希望Mixin有一种方法可以用作吞咽sys.stdout的装饰器,但是到目前为止我还没有成功。

我的自定义装饰器应该:

  • 受测试的函数产生的吞咽sys.stdout
  • 使用unittest.mock.patch函数作为装饰器以实现
  • 不接受任何输入(以保持代码干净)

我对装饰器的尝试:

import io
import sys
import unittest.mock

class StdoutUnittestMixin(unittest.TestCase):
    @unittest.mock.patch('sys.stdout', new_callable=io.StringIO)
    def monkey_patch_stdout(self, original_function, mock_stdout):
        def wrapper_function(*args, **kwargs):
            captured_output = io.StringIO() # Create StringIO object
            sys.stdout = captured_output    # and redirect stdout.
            return_value = original_function(*args, **kwargs) # Call unchanged function.
            sys.stdout = sys.__stdout__     # Reset redirect.
            return return_value
        return wrapper_function

已测试功能的示例:

def foo(some_str):
    print(some_str)
    return some_str.isnumeric()

在单元测试中需要使用修饰器:

class Testing(unittest.TestCase):
    @monkey_patch_stdout # The decorator wants inputs - I don't want that
    def test_function_outputs_true(self):
        self.assertTrue(foo("123"))

按预期,我得到:

TypeError: assert_True() missing 1 required positional argument: 'mock_stdout'

因此,我知道mock_stdout必须在那儿,因为unittest.mock.patch装饰者需要这样做。

1 个答案:

答案 0 :(得分:0)

我发现有两个问题: 1)正如@kdojeteri通知我的,谢谢,使用IN装饰器复制了猴子补丁逻辑 2)unittest.patch.mock行弄乱了事情。当我将对sys.stdout = sys.__stdout__的引用保存到一个新变量中,然后使用它来重置sys.stdout时,它起作用了。

尽管不满足我的要求之一,但最终的工作代码如下。

mixin方法:

sys.stdout

已测试的功能:

import io
import sys
import unittest.mock

class StdoutUnittestMixin(unittest.TestCase):
        @staticmethod
    def monkey_patch_stdout(original_function):
        """
        :param original_function: Decorated function which is expected to have stdout
        :return: Wrapped function which uses monkey patching for the sys.stdout object
        """
        def wrapper_function(*args, **kwargs):
            captured_output = io.StringIO()                     # Create StringIO object
            my_stdout = sys.stdout                              # Saving reference for sys.stdout
            sys.stdout = captured_output                        # and redirect stdout.
            return_value = original_function(*args, **kwargs)   # Call unchanged function.
            sys.stdout = my_stdout                              # Reset redirect.
            return return_value
        return wrapper_function

测试方法:

def foo(some_str):
    print(some_str)
    return some_str.isnumeric()