program story

파이썬에서 nosetest / unittest로 출력을 어설 션하는 방법은 무엇입니까?

inputbox 2020. 8. 17. 08:55
반응형

파이썬에서 nosetest / unittest로 출력을 어설 션하는 방법은 무엇입니까?


다음과 같은 기능에 대한 테스트를 작성하고 있습니다.

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 매개 변수로 nosetest를 실행하면 테스트가 중단됩니다. unittest 또는 nose 모듈로 출력을 어떻게 잡을 수 있습니까?


컨텍스트 관리자 를 사용하여 출력을 캡처합니다. 일시적으로 대체하여 궁극적으로 다른 답변 중 일부와 동일한 기술을 사용합니다 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

다음과 같이 사용하십시오.

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블록 을 종료하면 원래 출력 상태가 복원 되므로 첫 번째 캡처 블록과 동일한 기능으로 두 번째 캡처 블록을 설정할 수 있습니다. 이는 설정 및 해체 기능을 사용할 수 없으며 try-finally를 작성할 때 말이 많아집니다. 수동으로 차단합니다. 이 기능은 테스트의 목표가 미리 계산 된 값보다는 서로 상대적인 두 함수의 결과를 비교하는 것이었을 때 유용했습니다.


정말로이 작업을 수행하려면 테스트 기간 동안 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!'

Since version 2.7, you do not need anymore to reassign sys.stdout, this is provided through buffer flag. Moreover, it is the default behavior of 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. The opposite is achieved through nosetest flag --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.

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

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

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


Based on Rob Kennedy's answer, I wrote a class-based version of the context manager to buffer the output.

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. 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"

참고URL : https://stackoverflow.com/questions/4219717/how-to-assert-output-with-nosetest-unittest-in-python

반응형