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


114

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

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 모듈로 출력을 어떻게 잡을 수 있습니까?


답변:


124

컨텍스트 관리자 를 사용하여 출력을 캡처합니다. 일시적으로 대체하여 궁극적으로 다른 답변 중 일부와 동일한 기술을 사용합니다 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를 작성할 때 말이 많아집니다. 수동으로 차단합니다. 이 기능은 테스트의 목표가 미리 계산 된 값보다는 서로 상대적인 두 함수의 결과를 비교하는 것이었을 때 유용했습니다.


이것은 pep8radius 에서 나를 위해 정말 잘 작동했습니다 . 그러나 최근에 이것을 다시 사용하고 인쇄 할 때 다음과 같은 오류가 발생했습니다 (인쇄에 TypeError: unicode argument expected, got 'str'전달 된 유형 (str / unicode)은 관련이 없음).
Andy Hayden 2014 년

9
흠 그것은 파이썬 2에서는 우리가 원하는 from io import BytesIO as StringIO것이고 파이썬 3에서는 단지 from io import StringIO. 내 테스트에서 문제를 해결하는 것 같았습니다.
Andy Hayden 2014 년

4
죄송합니다. 너무 많은 메시지에 대해 사과드립니다. 이것을 찾는 사람들을 위해 명확히하기 위해 : python3은 io.StringIO를 사용하고, python 2는 StringIO.StringIO를 사용합니다! 다시 한 번 감사드립니다!
Andy Hayden 2014 년

여기 strip()에있는 모든 예제 가에서 unicode반환 된 이유는 무엇 StringIO.getvalue()입니까?
Palimondo 2017-06-04

1
아니, @Vedran. 이것은에 속한 이름을 다시 바인딩하는 데 의존합니다 sys. import 문을 사용하여 .NET 의 값 복사본 을받은 로컬 변수를 생성합니다 . 하나의 변경 사항은 다른 하나에 반영되지 않습니다. stderrsys.stderr
Rob Kennedy

60

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

11
참고 : python 3.x에서는 StringIO이제 io모듈 에서 클래스를 가져와야 합니다. from io import StringIO파이썬 2.6 이상에서 작동합니다.
Bryan P

2
from io import StringIOpython 2에서 사용 하는 TypeError: unicode argument expected, got 'str'경우 인쇄 할 때 a 가 표시됩니다.
matiasg 2014 년

9
참고 : Python 3.4에서는 contextlib.redirect_stdout 컨텍스트 관리자를 사용하여 예외 안전 방식으로이를 수행 할 수 있습니다 .with redirect_stdout(out):
Lucretiel

2
당신은 할 필요가 없습니다. 당신은 saved_stdout = sys.stdout항상 이것에 대한 마법의 심판을 가지고 있습니다. sys.__stdout__예를 들어, 당신 sys.stdout = sys.__stdout__은 당신의 정리 에만 필요 합니다.
ThorSummoner

@ThorSummoner 감사합니다, 이것은 단지 내 테스트의 일부를 단순화 했습니다 ... 당신이 주연 한 스쿠버 를 위해 ... 작은 세상!
조나단 라인 하트

48

버전 2.7부터는 더 이상 재 할당 할 필요가 없으며 sys.stdout이는 bufferflag를 통해 제공됩니다 . 또한 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, --buffer또는 unittest.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)

이것은 --nocapture; 특히이 플래그가 설정되면 버퍼링 모드가 비활성화됩니다. 따라서 터미널에서 출력을 볼 수 있거나 예상 한 출력인지 테스트 할 수 있는 옵션이 있습니다 .
ijoseph

1
ipdb.set_trace ()와 같은 것을 사용할 때 디버깅이 매우 어렵 기 때문에 각 테스트에 대해이 기능을 켜고 끌 수 있습니까?
Lqueryvg

33

from StringIO import StringIOPython 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')

3
나는 파이썬 3을 위해 이것을 좋아합니다. 깨끗합니다!
Sylhare

1
이것은이 페이지에서 나를 위해 일한 유일한 솔루션이었습니다! 감사합니다.
Justin Eyster

24

파이썬 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에서 추가되었습니다.
Hypercube

redirect_stdout의 경우 3.4이고 redirect_stderr의 경우 3.5입니다. 아마도 그것이 혼란이 발생한 곳일 것입니다!
rbennell

redirect_stdout()그리고 redirect_stderr()자신의 입력 인수를 돌려줍니다. 따라서 with contextlib.redirect_stdout(StringIO()) as temp_stdout:모든 것을 한 줄로 제공합니다. 3.7.1로 테스트되었습니다.
Adrian W

17

나는 단지 파이썬을 배우는 중이고 출력이있는 메서드에 대한 단위 테스트로 위의 문제와 유사한 문제로 어려움을 겪고 있음을 발견했습니다. 위의 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')

5
:) sys.stdout.getvalue().strip()와 비교하여 속임수 를 사용하지 않을 수 있습니다.\n
Silviu

StringIO 모듈은 더 이상 사용되지 않습니다. 대신from io import StringIO
Edwarric

10

테스트를 작성하는 것은 종종 코드를 작성하는 더 나은 방법을 보여줍니다. Shane의 대답과 마찬가지로 이것을 보는 또 다른 방법을 제안하고 싶습니다. 프로그램 이 특정 문자열 을 출력 했거나 출력을 위해 특정 문자열을 구성했다고 정말로 주장 하시겠습니까? Python print문이 제대로 작동 한다고 가정 할 수 있기 때문에 테스트하기가 더 쉬워집니다 .

def foo_msg():
    return 'hello world'

def foo():
    print foo_msg()

그렇다면 테스트는 매우 간단합니다.

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

물론, 프로그램의 실제 출력을 테스트해야 할 필요가 있다면 무시해도됩니다. :)


1
하지만이 경우 foo는에 어쩌면 문제입니다 ... 테스트되지 않습니다
페드로 발렌시아

5
순수 주의자의 관점에서 보면 아마도 문제 일 것입니다. 실용적인 관점에서 foo()아무것도하지 않고 print 문을 호출하면 문제가 되지 않을 것입니다.
Alison R.

5

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

2

또는 사용을 고려하십시오 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"

잘 했어. 링크가 사라지고 내용이 변경 될 수 있으므로 최소한의 예를 포함 할 수 있습니까?
KobeJohn

2

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)

0

이 스레드의 모든 멋진 답변을 바탕으로 이것이 제가 해결 한 방법입니다. 가능한 한 재고로 유지하고 싶었습니다. 내가 사용하는 단위 테스트 메커니즘 증강 setUp()캡처를 sys.stdout하고 sys.stderr, 새로운 어설 션 API를 예상 값과 캡처 된 값을 확인하고 복원하기 위해 추가 sys.stdoutsys.stderrtearDown(). I did this to keep a similar unit test API as the built-in유닛 테스트 API while still being able to unit test values printed tosys.stdout orsys.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)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.