简体   繁体   English

如何在 python 中使用 nosetest/unittest 断言输出?

[英]How to assert output with nosetest/unittest in python?

I'm writing tests for a function like next one:我正在为下一个函数编写测试:

def foo():
    print 'hello world!'

So when I want to test this function the code will be like this:所以当我想测试这个功能时,代码将是这样的:

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!'

But if I run nosetests with -s parameter the test crashes.但是,如果我使用 -s 参数运行 nosetests,测试就会崩溃。 How can I catch the output with unittest or nose module?如何使用 unittest 或 nose 模块捕获输出?

I use this context manager to capture output.我使用这个上下文管理器来捕获输出。 It ultimately uses the same technique as some of the other answers by temporarily replacing sys.stdout .它最终通过临时替换sys.stdout使用与其他一些答案相同的技术。 I prefer the context manager because it wraps all the bookkeeping into a single function, so I don't have to re-write any try-finally code, and I don't have to write setup and teardown functions just for this.我更喜欢上下文管理器,因为它将所有簿记包装到一个函数中,所以我不必重新编写任何 try-finally 代码,也不必为此编写设置和拆卸函数。

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

Use it like this:像这样使用它:

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!')

Furthermore, since the original output state is restored upon exiting the with block, we can set up a second capture block in the same function as the first one, which isn't possible using setup and teardown functions, and gets wordy when writing try-finally blocks manually.此外,由于原始输出状态在退出with块时恢复,我们可以在与第一个捕获块相同的函数中设置第二个捕获块,这使用设置和拆卸函数是不可能的,并且在编写 try- 时变得冗长最后手动阻止。 That ability came in handy when the goal of a test was to compare the results of two functions relative to each other rather than to some precomputed value.当测试的目标是将两个函数的结果相互比较而不是与某个预先计算的值进行比较时,这种能力就派上用场了。

If you really want to do this, you can reassign sys.stdout for the duration of the test.如果你真的想这样做,你可以在测试期间重新分配 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

If I were writing this code, however, I would prefer to pass an optional out parameter to the foo function.但是,如果我正在编写这段代码,我更愿意将一个可选的out参数传递给foo函数。

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

Then the test is much simpler:那么测试就简单多了:

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

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

Since version 2.7, you do not need anymore to reassign sys.stdout , this is provided through buffer flag .从 2.7 版开始,您不再需要重新分配sys.stdout ,这是通过buffer flag提供的。 Moreover, it is the default behavior of nosetest.而且,这是 nosetest 的默认行为。

Here is a sample failing in non buffered context:这是在非缓冲上下文中失败的示例:

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!')

You can set buffer through unit2 command line flag -b , --buffer or in unittest.main options.您可以通过unit2命令行标志-b--buffer或在unittest.main选项中设置缓冲区。 The opposite is achieved through nosetest flag --nocapture .相反的是通过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)

A lot of these answers failed for me because you can't from StringIO import StringIO in Python 3. Here's a minimum working snippet based on @naxa's comment and the Python Cookbook.很多这些答案对我来说都失败了,因为你不能在 Python 3 中from StringIO import StringIO 。这是基于@naxa 的评论和 Python Cookbook 的最小工作片段。

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')

In python 3.5 you can use contextlib.redirect_stdout() and StringIO() .在 python 3.5 中,您可以使用 contextlib.redirect_stdout( contextlib.redirect_stdout()StringIO() Here's the modification to your code这是对您的代码的修改

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!'

I'm only just learning Python and found myself struggling with a similar problem to the one above with unit tests for methods with output.我只是刚刚学习 Python,发现自己正在努力解决与上述问题类似的问题,即对具有输出的方法进行单元测试。 My passing unit test for foo module above has ended up looking like this:我通过上面的 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')

Writing tests often shows us a better way to write our code.编写测试通常会向我们展示一种更好的编写代码的方法。 Similar to Shane's answer, I'd like to suggest yet another way of looking at this.与 Shane 的回答类似,我想建议另一种看待这个问题的方法。 Do you really want to assert that your program outputted a certain string, or just that it constructed a certain string for output?您真的要断言您的程序输出了某个字符串,还是只是构造了某个字符串用于输出? This becomes easier to test, since we can probably assume that the Python print statement does its job correctly.这变得更容易测试,因为我们可以假设 Python print语句正确地完成了它的工作。

def foo_msg():
    return 'hello world'

def foo():
    print foo_msg()

Then your test is very simple:那么你的测试就很简单了:

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

Of course, if you really have a need to test your program's actual output, then feel free to disregard.当然,如果你真的需要测试你的程序的实际输出,那么可以无视。 :) :)

Both n611x007 and Noumenon already suggested using unittest.mock , but this answer adapts Acumenus's to show how you can easily wrap unittest.TestCase methods to interact with a mocked stdout . n611x007Noumenon都已经建议使用unittest.mock ,但是这个答案改编了 Acumenus以展示如何轻松包装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)

Based on Rob Kennedy's answer, I wrote a class-based version of the context manager to buffer the output.根据 Rob Kennedy 的回答,我编写了一个基于类的上下文管理器版本来缓冲输出。

Usage is like:用法是这样的:

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

Here's the implementation:这是实现:

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()

Or consider using pytest , it has built-in support for asserting stdout and stderr.或者考虑使用pytest ,它内置了对断言 stdout 和 stderr 的支持。 See 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"

Unittest ships with a context manager now (Python 3.7, but maybe earlier versions as well). Unittest 现在附带上下文管理器(Python 3.7,但也可能是更早的版本)。 You can just do this:你可以这样做:

# example.py

import logging

def method_with_logging():
    logging.info("Hello, World!")

Then in your unit test:然后在你的单元测试中:

# 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

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

Building on all the awesome answers in this thread, this is how I solved it.基于这个线程中所有很棒的答案,这就是我解决它的方法。 I wanted to keep it as stock as possible.我想尽可能保留它。 I augmented the unit test mechanism using setUp() to capture sys.stdout and sys.stderr , added new assert APIs to check the captured values against an expected value and then restore sys.stdout and sys.stderr upon tearDown(). I did this to keep a similar unit test API as the built-in我使用setUp()增强了单元测试机制以捕获sys.stdoutsys.stderr ,添加了新的断言 API 以根据预期值检查捕获的值,然后在tearDown(). I did this to keep a similar unit test API as the built-in时恢复sys.stdoutsys.stderr tearDown(). I did this to keep a similar unit test API as the built-in unittest API while still being able to unit test values printed to sys.stdout or sys.stderr`. tearDown(). I did this to keep a similar unit test API as the built-in API while still being able to unit test values printed to sys.stdout or sys.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()

When the unit test is run, the output is:运行单元测试时,输出为:

$ 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)

I like sorens' straightforward [Answer][1] to the question and sample code, particularly since I'm not familiar with newer features like patch/mock.我喜欢sorens 对问题和示例代码的直截了当的 [Answer][1],特别是因为我不熟悉 patch/mock 等较新的功能。 sorens didn't suggest a way to make the custom assertion methods of the example code's TestStdIO class reusable without resorting to cut/paste, so I took the approach of making TestStdIO a "mixin" class defined in its own module ( teststdoutmethods.py in the following example). sorens没有建议一种方法来使示例代码的TestStdIO类的自定义断言方法在不借助剪切/粘贴的情况下可重用,因此我采用了使TestStdIO成为在其自己的模块中定义的“mixin”类的方法( teststdoutmethods.py中下面的例子)。 Since the usual unittest.TestCase -provided assert method references used in TestStdIO will also be available in the test case class, I removed the import unittest line from his sample code and also the derivation of TestStdIO from unittest.TestCase in the class declaration, ie,由于通常在 TestStdIO 中使用的unittest.TestCase提供的断言方法引用也将在测试用例类中可用,因此我从他的示例代码中删除了import unittest行,并在类声明中从unittest.TestCase中删除了TestStdIO的派生,即,

import io
import sys

class TestStdIO(object):
    def setUp(self):
        ...

Otherwise the code of TestStdIO is as sorens' version sans the two example usages at the end.否则,TestStdIO 的代码与 sorens 的版本一样,最后没有两个示例用法。 I used this mixin class version of TestStdIO in some simple unittest test cases of a class in one of the basic example text games in Ch.我在 Ch 中的一个基本示例文本游戏中的一个类的一些简单单元测试测试用例中使用了TestStdIO的这个混合类版本。 2 of Kinsley and McGugan's Beginning Python Game Programming with PyGame , eg Kinsley 和 McGugan 的Beginning Python Game Programming with PyGame中的 2 个,例如

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')

The test cases (both successes and ginned failures) worked in Python 3.7.测试用例(成功和失败)在 Python 3.7 中运行。 Note that sorens' technique captures all of the stdout output between the setup() and teardown() calls, so I placed these around the specific actions that would generate the specific output I wanted to check.请注意, sorens 的技术捕获了 setup() 和 teardown() 调用之间的所有标准输出输出,因此我将这些放在将生成我想要检查的特定输出的特定操作周围。 I presume my mixin approach is what sorens would have intended for general reuse, but I'd like to know if anyone has a different recommendation.我认为我的 mixin 方法是sorens打算用于一般重用的方法,但我想知道是否有人有不同的建议。 Thx.谢谢。 [1]: https://stackoverflow.com/a/62429695/7386731 [1]: https ://stackoverflow.com/a/62429695/7386731

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM