<질문>
파이썬으로 단위 테스트를 작성하는 경우 (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),
'개발 > Python' 카테고리의 다른 글
Python에서 병렬 프로그래밍을 수행하는 방법은 무엇입니까? (0) | 2021.01.21 |
---|---|
PyPlot을 사용하여 부드러운 선 플로팅 (0) | 2021.01.15 |
Python / Django : runserver에서 콘솔에 로그, Apache에서 파일에 로그 (0) | 2021.01.15 |
[파이썬] pip를 사용한 SciPy 및 NumPy 설치 질문 (0) | 2021.01.14 |