【问题标题】:How to assert output with nosetest/unittest in python?python - 如何在python中使用nosetest/unittest断言输出?
【发布时间】:2011-05-12 07:22:45
【问题描述】:

我正在为类似下一个的函数编写测试:

def foo():
    print 'hello world!'

所以当我想测试这个功能时,代码会是这样的:

import sys
from foomodule import foo
def test_foo():
    foo()
    output = sys.stdout.getline().strip() # because stdout is an StringIO instance
    assert output == 'hello world!'

但如果我使用 -s 参数运行鼻子测试,测试会崩溃。如何使用 unittest 或 nose 模块捕获输出?

【问题讨论】:

标签: python unit-testing nosetests python-nose


【解决方案1】:

我使用这个context manager 来捕获输出。通过临时替换sys.stdout,它最终使用了与其他一些答案相同的技术。我更喜欢上下文管理器,因为它将所有簿记包装到一个函数中,因此我不必重新编写任何 try-finally 代码,也不必为此编写 setup 和 teardown 函数。

import sys
from contextlib import contextmanager
from StringIO import StringIO

@contextmanager
def captured_output():
    new_out, new_err = StringIO(), StringIO()
    old_out, old_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = new_out, new_err
        yield sys.stdout, sys.stderr
    finally:
        sys.stdout, sys.stderr = old_out, old_err

像这样使用它:

with captured_output() as (out, err):
    foo()
# This can go inside or outside the `with` block
output = out.getvalue().strip()
self.assertEqual(output, 'hello world!')

此外,由于在退出 with 块时会恢复原始输出状态,因此我们可以在与第一个函数相同的函数中设置第二个捕获块,这是使用 setup 和 teardown 函数无法实现的,并且得到手动编写 try-finally 块时罗嗦。当测试的目标是比较两个函数的结果,而不是与某个预先计算的值进行比较时,这种能力就派上用场了。

【讨论】:

  • 这在pep8radius 中对我来说非常有效。然而最近,我再次使用它并在打印 TypeError: unicode argument expected, got 'str' 时出现以下错误(传递给 print (str/unicode) 的类型无关)。
  • 嗯,这可能是在 python 2 中我们想要 from io import BytesIO as StringIO 而在 python 3 中只是 from io import StringIO。我认为似乎在我的测试中解决了这个问题。
  • 哎呀,刚刚结束,为这么多消息道歉。只是为了澄清人们发现这一点:python3 使用 io.StringIO,python 2 使用 StringIO.StringIO!再次感谢!
  • 为什么这里所有的例子都在StringIO.getvalue()返回的unicode上调用strip()
  • 不,@Vedran。这依赖于重新绑定属于sys 的名称。使用您的导入语句,您将创建一个名为stderr本地 变量,该变量接收sys.stderr 中值的副本。对其中一项的更改不会反映在另一项中。
【解决方案2】:

如果你真的想这样做,你可以在测试期间重新分配 sys.stdout。

def test_foo():
    import sys
    from foomodule import foo
    from StringIO import StringIO

    saved_stdout = sys.stdout
    try:
        out = StringIO()
        sys.stdout = out
        foo()
        output = out.getvalue().strip()
        assert output == 'hello world!'
    finally:
        sys.stdout = saved_stdout

但是,如果我正在编写此代码,我更愿意将可选的 out 参数传递给 foo 函数。

def foo(out=sys.stdout):
    out.write("hello, world!")

那么测试就简单多了:

def test_foo():
    from foomodule import foo
    from StringIO import StringIO

    out = StringIO()
    foo(out=out)
    output = out.getvalue().strip()
    assert output == 'hello world!'

【讨论】:

  • 注意:在 python 3.x 下,StringIO 类现在必须从 io 模块导入。 from io import StringIO 适用于 python 2.6+。
  • 如果在 python 2 中使用from io import StringIO,打印时会得到TypeError: unicode argument expected, got 'str'
  • 快速说明:在 python 3.4 中,您可以使用 contextlib.redirect_stdout 上下文管理器以异常安全的方式执行此操作:with redirect_stdout(out):
  • 你不需要做saved_stdout = sys.stdout,你总是在sys.__stdout__有一个神奇的参考,例如,你只需要sys.stdout = sys.__stdout__在你的清理中。
  • @ThorSummoner 谢谢,这只是简化了我的一些测试......对于scuba,我看到你已经出演了......小世界!
【解决方案3】:

从 2.7 版开始,您不再需要重新分配 sys.stdout,这是通过 buffer flag 提供的。而且,这是nosetest的默认行为。

这是一个在非缓冲上下文中失败的示例:

import sys
import unittest

def foo():
    print 'hello world!'

class Case(unittest.TestCase):
    def test_foo(self):
        foo()
        if not hasattr(sys.stdout, "getvalue"):
            self.fail("need to run in buffered mode")
        output = sys.stdout.getvalue().strip() # because stdout is an StringIO instance
        self.assertEquals(output,'hello world!')

您可以通过unit2 命令行标志-b--bufferunittest.main 选项设置缓冲区。 反之则通过nosetest 标志--nocapture 实现。

if __name__=="__main__":   
    assert not hasattr(sys.stdout, "getvalue")
    unittest.main(module=__name__, buffer=True, exit=False)
    #.
    #----------------------------------------------------------------------
    #Ran 1 test in 0.000s
    #
    #OK
    assert not hasattr(sys.stdout, "getvalue")

    unittest.main(module=__name__, buffer=False)
    #hello world!
    #F
    #======================================================================
    #FAIL: test_foo (__main__.Case)
    #----------------------------------------------------------------------
    #Traceback (most recent call last):
    #  File "test_stdout.py", line 15, in test_foo
    #    self.fail("need to run in buffered mode")
    #AssertionError: need to run in buffered mode
    #
    #----------------------------------------------------------------------
    #Ran 1 test in 0.002s
    #
    #FAILED (failures=1)

【讨论】:

  • 注意这与--nocapture交互;特别是,如果设置了这个标志,缓冲模式将被禁用。因此,您可以选择能够在终端上看到输出,能够测试输出是否符合预期。
  • 是否可以为每个测试打开和关闭它,因为这使得使用 ipdb.set_trace() 之类的东西时调试非常困难?
【解决方案4】:

很多这些答案对我来说都失败了,因为您不能在 Python 3 中使用from StringIO import StringIO。这是基于@naxa 的评论和 Python Cookbook 的最低工作 sn-p。

from io import StringIO
from unittest.mock import patch

with patch('sys.stdout', new=StringIO()) as fakeOutput:
    print('hello world')
    self.assertEqual(fakeOutput.getvalue().strip(), 'hello world')

【讨论】:

  • 我喜欢 Python 3 的这个,它很干净!
  • 这是此页面上唯一对我有用的解决方案!谢谢。
【解决方案5】:

在 python 3.5 中,您可以使用 contextlib.redirect_stdout()StringIO()。这是对您的代码的修改

import contextlib
from io import StringIO
from foomodule import foo

def test_foo():
    temp_stdout = StringIO()
    with contextlib.redirect_stdout(temp_stdout):
        foo()
    output = temp_stdout.getvalue().strip()
    assert output == 'hello world!'

【讨论】:

  • 很好的答案!根据文档,这是在 Python 3.4 中添加的。
  • redirect_stdout 为 3.4,redirect_stderr 为 3.5。也许这就是产生混乱的地方!
  • redirect_stdout()redirect_stderr() 返回它们的输入参数。所以,with contextlib.redirect_stdout(StringIO()) as temp_stdout: 在一行中为您提供所有信息。用 3.7.1 测试。
【解决方案6】:

我只是在学习 Python,发现自己遇到了与上述类似的问题,即对带有输出的方法进行单元测试。我通过上面的 foo 模块的单元测试最终看起来像这样:

import sys
import unittest
from foo import foo
from StringIO import StringIO

class FooTest (unittest.TestCase):
    def setUp(self):
        self.held, sys.stdout = sys.stdout, StringIO()

    def test_foo(self):
        foo()
        self.assertEqual(sys.stdout.getvalue(),'hello world!\n')

【讨论】:

  • 你可能想做一个sys.stdout.getvalue().strip()而不是作弊比较\n:)
  • StringIO 模块已弃用。而是from io import StringIO
【解决方案7】:

编写测试通常会向我们展示编写代码的更好方法。与 Shane 的回答类似,我想提出另一种看待这个问题的方法。你真的想断言你的程序输出了一个特定的字符串,还是只是为了输出构造了一个特定的字符串?这变得更容易测试,因为我们可以假设 Python print 语句正确地完成了它的工作。

def foo_msg():
    return 'hello world'

def foo():
    print foo_msg()

那么你的测试就很简单了:

def test_foo_msg():
    assert 'hello world' == foo_msg()

当然,如果您确实需要测试程序的实际输出,请随意忽略。 :)

【讨论】:

  • 但在这种情况下 foo 不会被测试...也许这是个问题
  • 从测试纯粹主义者的角度来看,这可能是个问题。从实际的角度来看,如果 foo() 除了调用 print 语句之外什么都不做,那可能不会有问题。
【解决方案8】:

根据 Rob Kennedy 的回答,我编写了一个基于类的上下文管理器版本来缓冲输出。

用法如下:

with OutputBuffer() as bf:
    print('hello world')
assert bf.out == 'hello world\n'

下面是实现:

from io import StringIO
import sys


class OutputBuffer(object):

    def __init__(self):
        self.stdout = StringIO()
        self.stderr = StringIO()

    def __enter__(self):
        self.original_stdout, self.original_stderr = sys.stdout, sys.stderr
        sys.stdout, sys.stderr = self.stdout, self.stderr
        return self

    def __exit__(self, exception_type, exception, traceback):
        sys.stdout, sys.stderr = self.original_stdout, self.original_stderr

    @property
    def out(self):
        return self.stdout.getvalue()

    @property
    def err(self):
        return self.stderr.getvalue()

【讨论】:

    【解决方案9】:

    n611x007Noumenon 都已建议使用 unittest.mock,但此答案适应了 Acumenus's 以展示如何轻松包装 unittest.TestCase 方法以与模拟的 stdout 交互。

    import io
    import unittest
    import unittest.mock
    
    msg = "Hello World!"
    
    
    # function we will be testing
    def foo():
        print(msg, end="")
    
    
    # create a decorator which wraps a TestCase method and pass it a mocked
    # stdout object
    mock_stdout = unittest.mock.patch('sys.stdout', new_callable=io.StringIO)
    
    
    class MyTests(unittest.TestCase):
    
        @mock_stdout
        def test_foo(self, stdout):
            # run the function whose output we want to test
            foo()
            # get its output from the mocked stdout
            actual = stdout.getvalue()
            expected = msg
            self.assertEqual(actual, expected)
    

    【讨论】:

      【解决方案10】:

      或者考虑使用pytest,它内置了对断言stdout和stderr的支持。见docs

      def test_myoutput(capsys): # or use "capfd" for fd-level
          print("hello")
          captured = capsys.readouterr()
          assert captured.out == "hello\n"
          print("next")
          captured = capsys.readouterr()
          assert captured.out == "next\n"
      

      【讨论】:

      • 好一个。由于链接可能会消失而内容可能会发生变化,您能否举一个最小的示例?
      【解决方案11】:

      Unittest 现在附带一个上下文管理器(Python 3.7,但也可能是更早的版本)。你可以这样做:

      # example.py
      
      import logging
      
      def method_with_logging():
          logging.info("Hello, World!")
      

      然后在你的单元测试中:

      # test.py
      
      from unittest import TestCase
      from example import method_with_logging
      
      class TestExample(TestCase):
          def test_logging(self):
              with self.assertLogs() as captured:
                  method_with_logging()
              self.assertEqual(len(captured.records), 1) # check that there is only one log message
              self.assertEqual(captured.records[0].getMessage(), "Hello, World!") # and it is the proper one
      

      取自https://pythonin1minute.com/how-to-test-logging-in-python/

      【讨论】:

      • 甜蜜,在 3.7 中像魅力一样工作
      【解决方案12】:

      在此线程中所有很棒的答案的基础上,这就是我解决它的方法。我想尽可能地保留它。我使用setUp() 增强了单元测试机制以捕获sys.stdoutsys.stderr,添加了新的断言API 以根据预期值检查捕获的值,然后在tearDown(). I did this to keep a similar unit test API as the built-inunittest@987654327 上恢复sys.stdoutsys.stderr @sys.stdoutorsys.stderr`。

      import io
      import sys
      import unittest
      
      
      class TestStdout(unittest.TestCase):
      
          # before each test, capture the sys.stdout and sys.stderr
          def setUp(self):
              self.test_out = io.StringIO()
              self.test_err = io.StringIO()
              self.original_output = sys.stdout
              self.original_err = sys.stderr
              sys.stdout = self.test_out
              sys.stderr = self.test_err
      
          # restore sys.stdout and sys.stderr after each test
          def tearDown(self):
              sys.stdout = self.original_output
              sys.stderr = self.original_err
      
          # assert that sys.stdout would be equal to expected value
          def assertStdoutEquals(self, value):
              self.assertEqual(self.test_out.getvalue().strip(), value)
      
          # assert that sys.stdout would not be equal to expected value
          def assertStdoutNotEquals(self, value):
              self.assertNotEqual(self.test_out.getvalue().strip(), value)
      
          # assert that sys.stderr would be equal to expected value
          def assertStderrEquals(self, value):
              self.assertEqual(self.test_err.getvalue().strip(), value)
      
          # assert that sys.stderr would not be equal to expected value
          def assertStderrNotEquals(self, value):
              self.assertNotEqual(self.test_err.getvalue().strip(), value)
      
          # example of unit test that can capture the printed output
          def test_print_good(self):
              print("------")
      
              # use assertStdoutEquals(value) to test if your
              # printed value matches your expected `value`
              self.assertStdoutEquals("------")
      
          # fails the test, expected different from actual!
          def test_print_bad(self):
              print("@=@=")
              self.assertStdoutEquals("@-@-")
      
      
      if __name__ == '__main__':
          unittest.main()
      

      运行单元测试时,输出为:

      $ python3 -m unittest -v tests/print_test.py
      test_print_bad (tests.print_test.TestStdout) ... FAIL
      test_print_good (tests.print_test.TestStdout) ... ok
      
      ======================================================================
      FAIL: test_print_bad (tests.print_test.TestStdout)
      ----------------------------------------------------------------------
      Traceback (most recent call last):
        File "/tests/print_test.py", line 51, in test_print_bad
          self.assertStdoutEquals("@-@-")
        File "/tests/print_test.py", line 24, in assertStdoutEquals
          self.assertEqual(self.test_out.getvalue().strip(), value)
      AssertionError: '@=@=' != '@-@-'
      - @=@=
      + @-@-
      
      
      ----------------------------------------------------------------------
      Ran 2 tests in 0.001s
      
      FAILED (failures=1)
      

      【讨论】:

        【解决方案13】:

        我喜欢 sorens' 对问题和示例代码直截了当的 [Answer][1],特别是因为我不熟悉补丁/模拟等新功能。 sorens 没有建议一种方法来使示例代码的 TestStdIO 类的自定义断言方法可以在不借助剪切/粘贴的情况下重复使用,因此我采用了使 TestStdIO 在其自己的模块中定义的“mixin”类(teststdoutmethods.py 在以下示例中)。由于 TestStdIO 中使用的通常 unittest.TestCase 提供的断言方法引用也将在测试用例类中可用,因此我从他的示例代码中删除了 import unittest 行,并且还TestStdIO 从类声明中的 unittest.TestCase 派生,即,

        import io
        import sys
        
        class TestStdIO(object):
            def setUp(self):
                ...
        

        否则,TestStdIO 的代码就像 sorens 的版本一样,没有最后的两个示例用法。 我在 Ch 中的一个基本示例文本游戏中的一个类的一些简单单元测试用例中使用了 TestStdIO 的这个 mixin 类版本。 2 Kinsley 和 McGugan 的 Beginning Python Game Programming with PyGame,例如

        import unittest
        from teststdoutmethods import TestStdIO   # sorens' TestStdIO as a mixin.
        from tank import Tank  # From Beginning Python Game Programming with PyGame.
        
        class Test_Tank_fire(TestStdIO, unittest.TestCase):   # Note multiple inheritance.
        
            def test_Tank_fire_wAmmo(self):
                oTank1 = Tank('Bill', 5, 100)
                oTank2 = Tank('Jim', 5, 100)
        
                self.setUp()
                oTank1.fire_at(oTank2)
        
                self.assertStdoutEquals("Bill fires on Jim\nJim is hit!")
                self.assertEqual(str(oTank1), 'Bill (100 Armor, 4 Ammo)', 'fire_at shooter attribute results incorrect')
                self.assertTrue(str(oTank2) == 'Jim (80 Armor, 5 Ammo)', 'fire_at target attribute results incorrect')
        
                self.tearDown()
        
            def test_Tank_fire_woAmmo(self):
                oTank1 = Tank('Bill', 5, 100)
                oTank2 = Tank('Jim', 5, 100)
        
                # Use up 5 allotted shots.
                for n in range(5):
                    oTank1.fire_at(oTank2)
        
                self.setUp()
                # Try one more.
                oTank1.fire_at(oTank2)
        
                self.assertStdoutEquals("Bill has no shells!")
        
                self.tearDown()
            
            def test_Tank_explode(self):
                oTank1 = Tank('Bill', 5, 100)
                oTank2 = Tank('Jim', 5, 100)
        
                # Use up 4 shots.
                for n in range(4):
                    oTank1.fire_at(oTank2)
        
                self.setUp()
                # Fifth shot should finish the target.
                oTank1.fire_at(oTank2)
        
                self.assertStdoutEquals("Bill fires on Jim\nJim is hit!\nJim explodes!")
                self.tearDown()
        
                self.assertTrue(str(oTank2) == 'Jim (DEAD)', 'fire_at target __str__ incorrect when Dead')
        

        测试用例(成功和失败)在 Python 3.7 中运行。请注意,sorens' 技术捕获了 setup() 和 teardown() 调用之间的所有 stdout 输出,因此我将它们放在会生成我想要检查的特定输出的特定操作周围。我认为我的 mixin 方法是 sorens 打算用于一般重用的,但我想知道是否有人有不同的建议。谢谢。 [1]:https://stackoverflow.com/a/62429695/7386731

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-09-16
          • 2015-02-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-01-07
          • 2022-11-17
          相关资源
          最近更新 更多