【问题标题】:Can't capture stdout with unittest无法使用 unittest 捕获标准输出
【发布时间】:2023-03-15 07:44:01
【问题描述】:

我有一个 python3.7 脚本,它以 YAML 文件作为输入,并根据其中的说明对其进行处理。我用于单元测试的 YAML 文件如下所示:

...
tasks:
  - echo '1'
  - echo '2'
  - echo '3'
  - echo '4'
  - echo '5'

脚本循环执行任务,然后使用os.system() 调用运行每个任务。

手动测试表明,输出符合预期:

1
2
3
4
5

但我无法让它在我的单元测试中工作。这是我尝试捕获输出的方式:

from application import application
from io import StringIO
import unittest
from unittest.mock import patch

class TestApplication(unittest.TestCase):
    def test_application_tasks(self):
        expected = ['1','2','3','4','5']

        with patch('sys.stdout', new=StringIO()) as fakeOutput:
            application.parse_event('some event') # print() is called here within parse_event()
            self.assertEqual(fakeOutput.getvalue().strip().split(), expected)

运行python3 -m unittest discover -s tests 时,我得到的只是AssertionError: Lists differ: [] != ['1', '2', '3', '4', '5']

我也尝试改用with patch('sys.stdout', new_callable=StringIO) as fakeOutput:,但无济于事。

我尝试的另一件事是self.assertEqual(fakeOutput.getvalue(), '1\n2\n3\n4\n5'),这是 unittest 输出的内容:

AssertionError: '' != '1\n2\n3\n4\n5'
+ 1
+ 2
+ 3
+ 4
+ 5

显然,该脚本工作并输出正确的结果,但fakeOutput 没有捕获它。

使用patch 作为装饰器也不起作用:

from application import application
from io import StringIO
import unittest
from unittest.mock import patch

class TestApplication(unittest.TestCase):
    @patch('sys.stdout', new_callable=StringIO)
    def test_application_tasks(self):
        expected = ['1','2','3','4','5']
        application.parse_event('some event') # print() is called here within parse_event()
        self.assertEqual(fakeOutput.getvalue().strip().split(), expected)

会输出完全相同的错误:AssertionError: Lists differ: [] != ['1', '2', '3', '4', '5']

【问题讨论】:

  • 您的问题不完整。 application的定义在哪里? print("Here is some output") 的简单定义不会重现您描述的行为。
  • 基于os.system 的提及,虽然我会猜测......
  • @Jean-PaulCalderone 是的,我已经意识到了......让我感到困惑的是我马上开始使用echo

标签: python-3.x stdout python-unittest


【解决方案1】:

您的解决方案很好,只需改用getvalue,如下所示:

with patch("sys.stdout", new_callable=StringIO) as f:
    print("Foo")
    r = f.getvalue()

print("r: {r!r} ;".format(r=r))

r: "Foo" ;

【讨论】:

    【解决方案2】:

    谢谢你,让-保罗·卡尔德隆。我意识到os.system() 创建了一个完全不同的过程,因此我需要以不同的方式解决问题,只有在我发布问题之后:)

    为了真正能够测试我的代码,我必须使用subprocess 而不是os.system() 重写它。最后,我使用subprocess_run_result = subprocess.run(task, shell=True, stdout=subprocess.PIPE),然后使用subprocess_run_result.stdout.strip().decode("utf-8") 得到结果。

    在测试中,我只是创建一个类的实例并调用一个方法,该方法在子进程中运行任务。

    如果有人想看一下,我的整个重构代码和测试是here in this commit

    【讨论】:

      【解决方案3】:

      os.system 运行一个新进程。如果你对sys.stdout 进行猴子补丁,这会影响当前进程,但不会对任何新进程产生影响。

      考虑:

      import sys
      
      from os import system
      from io import BytesIO
      
      capture = sys.stdout = BytesIO()
      system("echo Hello")
      sys.stdout = sys.__stdout__
      print(capture.getvalue())
      

      没有捕获任何内容,因为只有子进程已写入其标准输出。没有任何内容写入 Python 进程的标准输出。

      一般来说,避免os.system。相反,请使用subprocess 模块,它可以让您捕获正在运行的进程的输出。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-21
        • 1970-01-01
        • 2011-04-17
        相关资源
        最近更新 更多