개발/Python

Python의 단위 테스트에서 데이터 출력

MinorMan 2021. 1. 15. 08:22
반응형

<질문>

파이썬으로 단위 테스트를 작성하는 경우 (unittest 모듈 사용) 실패한 테스트에서 데이터를 출력 할 수 있으므로 오류의 원인을 추론하는 데 도움이되도록 검사 할 수 있습니까? 일부 정보를 전달할 수있는 사용자 지정 메시지를 만드는 기능을 알고 있지만 때로는 문자열로 쉽게 표현할 수없는 더 복잡한 데이터를 처리 할 수 있습니다.

예를 들어, Foo 클래스가 있고 testdata라는 목록의 데이터를 사용하여 메소드 표시 줄을 테스트한다고 가정하십시오.

class TestBar(unittest.TestCase):
    def runTest(self):
        for t1, t2 in testdata:
            f = Foo(t1)
            self.assertEqual(f.bar(t2), 2)

테스트가 실패하면이 특정 데이터가 실패한 이유를 확인하기 위해 t1, t2 및 / 또는 f를 출력 할 수 있습니다. 출력이란 테스트가 실행 된 후 다른 변수처럼 변수에 액세스 할 수 있음을 의미합니다.


<답변1>

저와 같은 사람이 간단하고 빠른 답변을 찾기 위해 여기에 오는 매우 늦은 답변입니다.

Python 2.7에서는 추가 매개 변수를 사용할 수 있습니다.msg다음과 같이 오류 메시지에 정보를 추가합니다.

self.assertEqual(f.bar(t2), 2, msg='{0}, {1}'.format(t1, t2))

공식 문서here


<답변2>

이를 위해 로깅 모듈을 사용합니다.

예를 들면 :

import logging
class SomeTest( unittest.TestCase ):
    def testSomething( self ):
        log= logging.getLogger( "SomeTest.testSomething" )
        log.debug( "this= %r", self.this )
        log.debug( "that= %r", self.that )
        # etc.
        self.assertEquals( 3.14, pi )

if __name__ == "__main__":
    logging.basicConfig( stream=sys.stderr )
    logging.getLogger( "SomeTest.testSomething" ).setLevel( logging.DEBUG )
    unittest.main()

이를 통해 실패하고 추가 디버깅 정보가 필요한 특정 테스트에 대해 디버깅을 켤 수 있습니다.

그러나 내가 선호하는 방법은 디버깅에 많은 시간을 소비하는 것이 아니라 문제를 드러내 기 위해 더 세분화 된 테스트를 작성하는 데 소비하는 것입니다.


<답변3>

간단한 print 문을 사용하거나 stdout에 쓰는 다른 방법을 사용할 수 있습니다. 테스트의 어느 곳에서나 Python 디버거를 호출 할 수도 있습니다.

사용하는 경우nose테스트를 실행하기 위해 (내가 권장하는) 각 테스트에 대한 stdout을 수집하고 테스트가 실패한 경우에만 표시하므로 테스트가 통과 할 때 복잡한 출력으로 살 필요가 없습니다.

nose에는 어설 션에 언급 된 변수를 자동으로 표시하거나 실패한 테스트에서 디버거를 호출하는 스위치도 있습니다. 예를 들면-s(--nocapture)는 stdout 캡처를 방지합니다.


<답변4>

나는 이것이 당신이 찾고있는 것이 아니라고 생각하지 않습니다. 실패하지 않는 변수 값을 표시하는 방법은 없지만 원하는 방식으로 결과를 출력하는 데 도움이 될 수 있습니다.

당신은 사용할 수 있습니다TestResult 객체에 의해 반환TestRunner.run ()결과 분석 및 처리를 위해. 특히 TestResult.errors 및 TestResult.failures

TestResults 개체 정보 :

http://docs.python.org/library/unittest.html#id3

그리고 올바른 방향을 가리키는 코드 :

>>> import random
>>> import unittest
>>>
>>> class TestSequenceFunctions(unittest.TestCase):
...     def setUp(self):
...         self.seq = range(5)
...     def testshuffle(self):
...         # make sure the shuffled sequence does not lose any elements
...         random.shuffle(self.seq)
...         self.seq.sort()
...         self.assertEqual(self.seq, range(10))
...     def testchoice(self):
...         element = random.choice(self.seq)
...         error_test = 1/0
...         self.assert_(element in self.seq)
...     def testsample(self):
...         self.assertRaises(ValueError, random.sample, self.seq, 20)
...         for element in random.sample(self.seq, 5):
...             self.assert_(element in self.seq)
...
>>> suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)
>>> testResult = unittest.TextTestRunner(verbosity=2).run(suite)
testchoice (__main__.TestSequenceFunctions) ... ERROR
testsample (__main__.TestSequenceFunctions) ... ok
testshuffle (__main__.TestSequenceFunctions) ... FAIL

======================================================================
ERROR: testchoice (__main__.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "", line 11, in testchoice
ZeroDivisionError: integer division or modulo by zero

======================================================================
FAIL: testshuffle (__main__.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "", line 8, in testshuffle
AssertionError: [0, 1, 2, 3, 4] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

----------------------------------------------------------------------
Ran 3 tests in 0.031s

FAILED (failures=1, errors=1)
>>>
>>> testResult.errors
[(<__main__.TestSequenceFunctions testMethod=testchoice>, 'Traceback (most recent call last):\n  File ""
, line 11, in testchoice\nZeroDivisionError: integer division or modulo by zero\n')]
>>>
>>> testResult.failures
[(<__main__.TestSequenceFunctions testMethod=testshuffle>, 'Traceback (most recent call last):\n  File "
", line 8, in testshuffle\nAssertionError: [0, 1, 2, 3, 4] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n')]
>>>

<답변5>

제가 사용하는 방법은 정말 간단합니다. 경고로 기록하기 만하면 실제로 표시됩니다.

import logging

class TestBar(unittest.TestCase):
    def runTest(self):

       #this line is important
       logging.basicConfig()
       log = logging.getLogger("LOG")

       for t1, t2 in testdata:
         f = Foo(t1)
         self.assertEqual(f.bar(t2), 2)
         log.warning(t1)

<답변6>

또 다른 옵션-테스트가 실패한 디버거를 시작합니다.

Testoob로 테스트를 실행 해보십시오 (변경없이 unittest 제품군을 실행합니다). 테스트가 실패 할 때 '--debug'명령 줄 스위치를 사용하여 디버거를 열 수 있습니다.

다음은 Windows의 터미널 세션입니다.

C:\work> testoob tests.py --debug
F
Debugging for failure in test: test_foo (tests.MyTests.test_foo)
> c:\python25\lib\unittest.py(334)failUnlessEqual()
-> (msg or '%r != %r' % (first, second))
(Pdb) up
> c:\work\tests.py(6)test_foo()
-> self.assertEqual(x, y)
(Pdb) l
  1     from unittest import TestCase
  2     class MyTests(TestCase):
  3       def test_foo(self):
  4         x = 1
  5         y = 2
  6  ->     self.assertEqual(x, y)
[EOF]
(Pdb)

<답변7>

나는 이것을 지나치게 생각했을 것 같다. 제가 생각 해낸 한 가지 방법은 진단 데이터를 축적하는 전역 변수를 갖는 것입니다.

다음과 같이 :

log1 = dict()
class TestBar(unittest.TestCase):
    def runTest(self):
        for t1, t2 in testdata:
            f = Foo(t1) 
            if f.bar(t2) != 2: 
                log1("TestBar.runTest") = (f, t1, t2)
                self.fail("f.bar(t2) != 2")

답장을 보내 주셔서 감사합니다. 그들은 파이썬에서 단위 테스트의 정보를 기록하는 방법에 대한 몇 가지 대안 아이디어를 제공했습니다.


<답변8>

로깅 사용 :

import unittest
import logging
import inspect
import os

logging_level = logging.INFO

try:
    log_file = os.environ["LOG_FILE"]
except KeyError:
    log_file = None

def logger(stack=None):
    if not hasattr(logger, "initialized"):
        logging.basicConfig(filename=log_file, level=logging_level)
        logger.initialized = True
    if not stack:
        stack = inspect.stack()
    name = stack[1][3]
    try:
        name = stack[1][0].f_locals["self"].__class__.__name__ + "." + name
    except KeyError:
        pass
    return logging.getLogger(name)

def todo(msg):
    logger(inspect.stack()).warning("TODO: {}".format(msg))

def get_pi():
    logger().info("sorry, I know only three digits")
    return 3.14

class Test(unittest.TestCase):

    def testName(self):
        todo("use a better get_pi")
        pi = get_pi()
        logger().info("pi = {}".format(pi))
        todo("check more digits in pi")
        self.assertAlmostEqual(pi, 3.14)
        logger().debug("end of this test")
        pass

용법:

# LOG_FILE=/tmp/log python3 -m unittest LoggerDemo
.
----------------------------------------------------------------------
Ran 1 test in 0.047s

OK
# cat /tmp/log
WARNING:Test.testName:TODO: use a better get_pi
INFO:get_pi:sorry, I know only three digits
INFO:Test.testName:pi = 3.14
WARNING:Test.testName:TODO: check more digits in pi

설정하지 않은 경우LOG_FILE, 로깅은stderr.


<답변9>

당신이 사용할 수있는logging그 모듈.

따라서 단위 테스트 코드에서 다음을 사용하십시오.

import logging as log

def test_foo(self):
    log.debug("Some debug message.")
    log.info("Some info message.")
    log.warning("Some warning message.")
    log.error("Some error message.")

기본적으로 경고 및 오류는/dev/stderr이므로 콘솔에 표시되어야합니다.

로그를 사용자 정의하려면 (예 : 형식화) 다음 샘플을 시도하십시오.

# Set-up logger
if args.verbose or args.debug:
    logging.basicConfig( stream=sys.stdout )
    root = logging.getLogger()
    root.setLevel(logging.INFO if args.verbose else logging.DEBUG)
    ch = logging.StreamHandler(sys.stdout)
    ch.setLevel(logging.INFO if args.verbose else logging.DEBUG)
    ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
    root.addHandler(ch)
else:
    logging.basicConfig(stream=sys.stderr)

<답변10>

이 경우 내가하는 일은log.debug()내 응용 프로그램에 몇 가지 메시지가 있습니다. 기본 로깅 수준은WARNING, 이러한 메시지는 정상적인 실행에서 표시되지 않습니다.

그런 다음 unittest에서 로깅 수준을DEBUG, 이러한 메시지는 실행 중에 표시됩니다.

import logging

log.debug("Some messages to be shown just when debugging or unittesting")

unittests에서 :

# Set log level
loglevel = logging.DEBUG
logging.basicConfig(level=loglevel)

전체 예보기 :

이것은daikiri.py, 이름과 가격으로 Daikiri를 구현하는 기본 클래스. 방법있어make_discount()주어진 할인을 적용한 후 특정 다이 키리의 가격을 반환합니다.

import logging

log = logging.getLogger(__name__)

class Daikiri(object):
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def make_discount(self, percentage):
        log.debug("Deducting discount...")  # I want to see this message
        return self.price * percentage

그런 다음 unittest를 만듭니다.test_daikiri.py사용법을 확인합니다.

import unittest
import logging
from .daikiri import Daikiri


class TestDaikiri(unittest.TestCase):
    def setUp(self):
        # Changing log level to DEBUG
        loglevel = logging.DEBUG
        logging.basicConfig(level=loglevel)

        self.mydaikiri = Daikiri("cuban", 25)

    def test_drop_price(self):
        new_price = self.mydaikiri.make_discount(0)
        self.assertEqual(new_price, 0)

if __name__ == "__main__":
    unittest.main()

그래서 내가 그것을 실행할 때 나는log.debug메시지 :

$ python -m test_daikiri
DEBUG:daikiri:Deducting discount...
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

<답변11>

inspect.trace를 사용하면 예외가 발생한 후 지역 변수를 가져올 수 있습니다. 그런 다음 다음과 같은 데코레이터로 단위 테스트를 래핑하여 사후 분석 중에 검사 할 지역 변수를 절약 할 수 있습니다.

import random
import unittest
import inspect


def store_result(f):
    """
    Store the results of a test
    On success, store the return value.
    On failure, store the local variables where the exception was thrown.
    """
    def wrapped(self):
        if 'results' not in self.__dict__:
            self.results = {}
        # If a test throws an exception, store local variables in results:
        try:
            result = f(self)
        except Exception as e:
            self.results[f.__name__] = {'success':False, 'locals':inspect.trace()[-1][0].f_locals}
            raise e
        self.results[f.__name__] = {'success':True, 'result':result}
        return result
    return wrapped

def suite_results(suite):
    """
    Get all the results from a test suite
    """
    ans = {}
    for test in suite:
        if 'results' in test.__dict__:
            ans.update(test.results)
    return ans

# Example:
class TestSequenceFunctions(unittest.TestCase):

    def setUp(self):
        self.seq = range(10)

    @store_result
    def test_shuffle(self):
        # make sure the shuffled sequence does not lose any elements
        random.shuffle(self.seq)
        self.seq.sort()
        self.assertEqual(self.seq, range(10))
        # should raise an exception for an immutable sequence
        self.assertRaises(TypeError, random.shuffle, (1,2,3))
        return {1:2}

    @store_result
    def test_choice(self):
        element = random.choice(self.seq)
        self.assertTrue(element in self.seq)
        return {7:2}

    @store_result
    def test_sample(self):
        x = 799
        with self.assertRaises(ValueError):
            random.sample(self.seq, 20)
        for element in random.sample(self.seq, 5):
            self.assertTrue(element in self.seq)
        return {1:99999}


suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)
unittest.TextTestRunner(verbosity=2).run(suite)

from pprint import pprint
pprint(suite_results(suite))

마지막 줄은 테스트가 성공한 경우 반환 된 값과 실패 할 경우 로컬 변수 (이 경우 x)를 인쇄합니다.

{'test_choice': {'result': {7: 2}, 'success': True},
 'test_sample': {'locals': {'self': <__main__.TestSequenceFunctions testMethod=test_sample>,
                            'x': 799},
                 'success': False},
 'test_shuffle': {'result': {1: 2}, 'success': True}}

재미 :-)


<답변12>

어설 션 실패에서 생성 된 예외를 포착하는 것은 어떻습니까? catch 블록에서 원하는 위치에 데이터를 출력 할 수 있습니다. 그런 다음 완료되면 예외를 다시 던질 수 있습니다. 테스트 러너는 아마 그 차이를 모를 것입니다.

면책 조항 : 저는 파이썬의 단위 테스트 프레임 워크에서 이것을 시도하지 않았지만 다른 단위 테스트 프레임 워크에서는 가지고 있습니다.


<답변13>

내가 그것을 시도하지 않았다는 것을 인정하고,testfixtures' logging feature꽤 유용 해 보입니다 ...


<답변14>

@FC의 답변을 확장하면 이것은 나에게 아주 잘 작동합니다.

class MyTest(unittest.TestCase):
    def messenger(self, message):
        try:
            self.assertEqual(1, 2, msg=message)
        except AssertionError as e:      
            print "\nMESSENGER OUTPUT: %s" % str(e),
반응형