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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4219717/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 14:49:04  来源:igfitidea点击:

How to assert output with nosetest/unittest in python?

pythonunit-testingnosetestspython-nose

提问by Pedro Valencia

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. How can I catch the output with unittest or nose module?

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

回答by Shane Hathaway

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 outparameter to the foofunction.

但是,如果我正在编写此代码,我更愿意将一个可选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!'

回答by Alison R.

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. Do you really want to assert that your program outputted a certain string, or just that it constructed a certain stringfor output? This becomes easier to test, since we can probably assume that the Python printstatement does its job correctly.

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

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. :)

当然,如果你真的有需要测试你的程序的实际输出,那么可以随意忽略。:)

回答by sean_robbins

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. My passing unit test for foo module above has ended up looking like this:

我只是在学习 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')

回答by FabienAndre

Since version 2.7, you do not need anymore to reassign sys.stdout, this is provided through bufferflag. Moreover, it is the default behavior of nosetest.

从 2.7 版开始,您不再需要重新分配sys.stdout,这是通过bufferflag提供的。此外,它是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 unit2command line flag -b, --bufferor in unittest.mainoptions. The opposite is achieved through nosetestflag --nocapture.

您可以设置缓冲区通过unit2命令行标志-b--bufferunittest.main选择。相反是通过nosetestflag实现的--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)

回答by Rob Kennedy

I use this context managerto capture output. It ultimately uses the same technique as some of the other answers by temporarily replacing 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.

我使用这个上下文管理器来捕获输出。它最终通过临时替换sys.stdout. 我更喜欢上下文管理器,因为它将所有簿记包装到一个函数中,所以我不必重新编写任何 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 withblock, 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. 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.

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

回答by Michel Samia

Or consider using pytest, it has built-in support for asserting stdout and stderr. See docs

或者考虑使用pytest,它内置支持断言 stdout 和 stderr。查看文档

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"

回答by Noumenon

A lot of these answers failed for me because you can't from StringIO import StringIOin Python 3. Here's a minimum working snippet based on @naxa's comment and the Python Cookbook.

很多这些答案对我来说都失败了,因为你不能from StringIO import StringIO在 Python 3 中。这是基于@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')

回答by Mudit Jain

In python 3.5 you can use contextlib.redirect_stdout()and StringIO(). Here's the modification to your code

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

回答by Hugo Mota

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

回答by rovyko

Both n611x007and Noumenonalready suggested using unittest.mock, but this answer adapts Acumenus'sto show how you can easily wrap unittest.TestCasemethods to interact with a mocked stdout.

无论n611x007本体使用已经建议unittest.mock,但这个答案适应阿卡门斯的向您展示如何轻松地包裹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)