source

파이썬에서 테스트/유니트 테스트 없이 출력을 주장하는 방법?

factcode 2023. 10. 11. 21:02
반응형

파이썬에서 테스트/유니트 테스트 없이 출력을 주장하는 방법?

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

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 파라미터를 사용하여 테스트를 실행하지 않으면 테스트가 충돌합니다.유니트 테스트 또는 노즈 모듈로 출력을 파악하려면 어떻게 해야 합니까?

출력을 캡처하기 위해 이 컨텍스트 매니저를 사용합니다.일시적으로 교체함으로써 궁극적으로 다른 답변들과 동일한 기술을 사용합니다.sys.stdout는 모든 작성할 기능을 가 없기 때문입니다 상황 관리자를 선호하는 이유는 모든 부기를 하나의 기능으로 포장하기 때문에 시도 코드를 다시 작성할 필요가 없고, 이것만을 위한 셋업과 해체 기능을 작성할 필요가 없기 때문입니다.

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블록, 우리는 첫 번째 것과 같은 기능으로 두 번째 캡처 블록을 설정할 수 있는데, 이것은 셋업과 해체 기능을 사용해서는 불가능하고, 시도해보기 블록을 수동으로 쓸 때 말수가 적어집니다.테스트의 목표가 사전 계산된 값이 아닌 서로 관련된 두 함수의 결과를 비교하는 것일 때 이 기능은 유용했습니다.

이렇게 하려면 테스트 기간 동안 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!'

더 이상 2.7 을 sys.stdout, 이것은 깃발을 통해서 제공됩니다.또한 테스트가 없는 기본 동작입니다.

버퍼링되지 않은 컨텍스트에서 실패하는 샘플은 다음과 같습니다.

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

를 할 수 .unit2g-b,--buffer아니면unittest.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)

이 했습니다를 할 없기 에 이 많은 했습니다.from StringIO import StringIO파이썬 3에서.여기 @naxa의 코멘트와 파이썬 쿡북을 기반으로 한 최소한의 작업 스니펫이 있습니다.

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 에서는를 할 수 .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!'

저는 단지 파이썬을 배우고 있을 뿐인데 출력이 있는 방법에 대한 단위 테스트로 위와 비슷한 문제를 겪고 있는 저 자신을 발견했습니다.위의 foo module에 대한 나의 합격 단위 테스트는 결국 다음과 같이 되었습니다.

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

쓰기 테스트는 종종 코드를 더 잘 쓰는 방법을 보여줍니다.셰인의 대답과 비슷하게, 저는 이것을 바라보는 또 다른 방법을 제안하고 싶습니다.당신의 프로그램이 특정 문자열을 출력했다고 주장하고 싶으십니까, 아니면 단지 출력을 위해 특정 문자열을 구성했다고 주장하고 싶으십니까?이것은 테스트하기 쉬워집니다. 왜냐하면 우리는 아마도 파이썬이print문이 올바르게 작동합니다.

def foo_msg():
    return 'hello world'

def foo():
    print foo_msg()

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

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

물론, 정말로 프로그램의 실제 출력을 테스트할 필요가 있다면 언제든지 무시하세요. :)

n611x007Numenon 둘 다 이미 사용을 제안했습니다.unittest.mock, 하지만 이 대답은 아큐메누스의 것을 어떻게 쉽게 포장할 수 있는지 보여주기 위해 사용합니다.unittest.TestCasestdout.

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)

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

합니다를 하는 것을 해 봅니다.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"

지금은 컨텍스트 관리자(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/ 에서 가져온 자료

이 스레드에 있는 모든 멋진 답들을 바탕으로, 이것이 제가 해결한 방법입니다.저는 가능한 한 재고를 보유하고 싶었습니다. 테스트 메커니즘을 하였습니다.setUp()하다, 하다, 포획하다, 포획하다, 포획.sys.stdout그리고.sys.stderr하여 캡처 후, new assert API 를했습니다를 합니다.sys.stdout그리고.sys.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.stdoutor .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)

저는 특히 패치/모의와 같은 새로운 기능에 익숙하지 않기 때문에 질문과 샘플 코드에 대한 sorens의 간단한 [답변]을 좋아합니다. sorens는 예제 코드의 TestStd의 사용자 정의 주장 방법을 만드는 방법을 제안하지 않았습니다.잘라내기/붙여넣기를 하지 않고 재사용 가능한 IO 클래스를 선택하여 TestStd를 만드는 방법을 취했습니다.IO는 자체 모듈(teststdoutmethods.py 에서 정의된 "믹스인" 클래스입니다.늘 하던 유닛 테스트 때부터.TestStd에서 사용되는 TestCase 제공 assert 방법 참조IO는 테스트 케이스 클래스에서도 사용 가능할 것입니다. 저는 그의 샘플 코드에서 수입 단위 테스트 라인을 제거했고 TestStd의 파생 사항도 제거했습니다.Unit test에서 IO.클래스 선언의 TestCase 즉,

import io
import sys

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

그렇지 않은 경우 TestStd 코드IO는 sorens의 버전과 마지막에 사용되는 두 가지 예시와 같습니다.믹스를 TestStd의 클래스 버전에서 사용했습니다.예를 들어, Kinsley의 Ch. 2와 McGugan의 PyGame으로 파이썬 게임 프로그래밍을 시작하는 기본 예제 텍스트 게임 중 하나에서 클래스의 간단한 유니트 테스트 사례에서 IO.

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의 기법은 설정()과 teardown() 호출 사이의 stdout 출력을 모두 캡처하기 때문에 확인하고자 하는 특정 출력을 생성할 특정 작업을 중심으로 배치했습니다.일반적인 재사용을 위해 소린이 의도했을 법한 저의 믹스인 접근 방식이라고 생각합니다만, 다른 추천을 받은 사람이 있는지 알고 싶습니다.Thx. [1]: https://stackoverflow.com/a/62429695/7386731

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

반응형