파이썬에서 단위 테스트에서 데이터 출력


115

파이썬으로 단위 테스트를 작성하는 경우 (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를 출력 할 수 있습니다. 출력에 따르면 테스트가 실행 된 후 다른 변수처럼 변수에 액세스 할 수 있습니다.

답변:


73

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

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

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

여기에 공식 문서


1
Python 3에서도 작동합니다.
MrDBA

18
문서는 이에 대한 힌트를 제공하지만 명시 적으로 언급 할 가치가 있습니다. 기본적 msg으로 사용되는 경우 일반 오류 메시지를 대체합니다. msg일반 오류 메시지에 추가 하려면 TestCase.longMessage 를 True 로 설정해야합니다.
Catalin Iacob

1
사용자 지정 오류 메시지를 전달할 수 있다는 사실을 알게되어 반가 웠지만 오류에 관계없이 일부 메시지를 인쇄하는 데 관심이있었습니다.
Harry Moreno

5
@CatalinIacob의 주석은 Python 2.x에 적용됩니다. Python 3.x에서 TestCase.longMessage의 기본값은 True.
ndmeiri

70

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

예를 들면 :

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

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

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


testSomething 내부에서 foo 메서드를 호출하고 무언가를 기록하면 어떻게 될까요? 로거를 foo에 전달하지 않고 출력을 어떻게 볼 수 있습니까?
simao 2010

@simao : 무엇입니까 foo? 별도의 기능? 의 메서드 함수 SomeTest? 첫 번째 경우 함수는 자체 로거를 가질 수 있습니다. 두 번째 경우, 다른 메소드 함수는 자체 로거를 가질 수 있습니다. logging패키지 작동 방식을 알고 있습니까? 여러 로거가 일반적입니다.
S.Lott

8
지정한 방식대로 로깅을 설정했습니다. 작동한다고 가정하지만 출력은 어디에서 볼 수 있습니까? 콘솔로 출력되지 않습니다. 파일에 로깅하여 구성을 시도했지만 출력도 생성되지 않습니다.
MikeyE

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

34

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

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

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


불행히도 nose는 로깅 프레임 워크를 사용하여 stdout / err에 기록 된 로그를 수집하지 않는 것 같습니다. 나는이 printlog.debug()서로 옆에, 그리고 명시 적으로 켜 DEBUG로부터 루트에 로깅 setUp()방법,하지만 단지 print출력 쇼까지.
haridsv

7
nosetests -s오류가 있는지 여부에 관계없이 stdout의 내용을 보여줍니다.
Hargriffle 2014 년

코 문서에서 변수를 자동으로 표시하는 스위치를 찾을 수 없습니다. 그것들을 설명하는 것을 알려줄 수 있습니까?
ABM

nose 또는 unittest에서 변수를 자동으로 표시하는 방법을 모르겠습니다. 테스트에서보고 싶은 것을 인쇄합니다.
네드 BATCHELDER

16

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

결과 분석 및 처리를 위해 TestRunner.run () 에서 반환 된 TestResult 개체를 사용할 수 있습니다 . 특히 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 "<stdin>", line 11, in testchoice
ZeroDivisionError: integer division or modulo by zero

======================================================================
FAIL: testshuffle (__main__.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<stdin>", 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 "<stdin>"
, 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 "<stdin>
", line 8, in testshuffle\nAssertionError: [0, 1, 2, 3, 4] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n')]
>>>

5

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

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)

2
Nose ( nose.readthedocs.org/en/latest/index.html )는 '디버거 세션 시작'옵션을 제공하는 또 다른 프레임 워크입니다. 출력을 먹지 않는 '-sx --pdb --pdb-failures'로 실행하고 첫 번째 실패 후 중지하고 예외 및 테스트 실패시 pdb로 떨어집니다. 이것은 내가 게으르고 루프에서 테스트하지 않는 한 풍부한 오류 메시지에 대한 필요성을 제거했습니다.
jwhitlock

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)

테스트가 성공하면 작동합니까? 제 경우에는 테스트가 실패한 경우에만 경고가 표시됩니다
Shreya Maria

@ShreyaMaria 예 그것은 것입니다
Orane

5

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

다음과 같이 :

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

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


2

로깅 사용 :

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.


2

이를 위해 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)

2

이 경우 내가하는 일은 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

그런 다음 test_daikiri.py사용량을 확인 하는 unittest 를 만듭니다 .

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

1

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

Har det gøy :-)


0

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

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



-1

@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),
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.