파이썬에서 단위 테스트에서 데이터 출력
파이썬으로 단위 테스트를 작성하는 경우 (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를 출력 할 수 있습니다. 출력에 따르면 테스트가 실행 된 후 다른 변수처럼 변수에 액세스 할 수 있습니다.
저처럼 간단하고 빠른 답변을 찾기 위해 여기에 오는 사람에게는 매우 늦은 답변입니다.
Python 2.7에서는 추가 매개 변수 msg
를 사용하여 다음과 같은 오류 메시지에 정보를 추가 할 수 있습니다 .
self.assertEqual(f.bar(t2), 2, msg='{0}, {1}'.format(t1, t2))
여기에 공식 문서
이를 위해 로깅 모듈을 사용합니다.
예를 들면 :
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()
이를 통해 실패하고 추가 디버깅 정보가 필요한 특정 테스트에 대해 디버깅을 켤 수 있습니다.
그러나 내가 선호하는 방법은 디버깅에 많은 시간을 소비하는 것이 아니라 문제를 드러내 기 위해 더 세분화 된 테스트를 작성하는 데 소비하는 것입니다.
간단한 print 문을 사용하거나 stdout에 쓰는 다른 방법을 사용할 수 있습니다. 테스트의 어느 곳에서나 Python 디버거를 호출 할 수도 있습니다.
nose 를 사용 하여 테스트를 실행 하면 (내가 권장하는) 각 테스트에 대한 stdout을 수집하고 테스트가 실패한 경우에만 표시하므로 테스트가 통과 할 때 복잡한 출력으로 살 필요가 없습니다.
nose는 또한 assert에 언급 된 변수를 자동으로 표시하거나 실패한 테스트에서 디버거를 호출하는 스위치를 가지고 있습니다. 예를 들어 -s
( --nocapture
)는 stdout 캡처를 방지합니다.
나는 이것이 당신이 찾고있는 것이 아니라고 생각하지 않습니다. 실패하지 않는 변수 값을 표시하는 방법은 없지만 원하는 방식으로 결과를 출력하는 데 도움이 될 수 있습니다.
결과 분석 및 처리를 위해 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')]
>>>
나는 이것을 지나치게 생각했을 것 같다. 제가 생각 해낸 한 가지 방법은 진단 데이터를 축적하는 전역 변수를 갖는 것입니다.
다음과 같이 :
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")
회신 해 주셔서 감사합니다. 그들은 단위 테스트에서 정보를 기록하는 방법에 대한 몇 가지 대안 아이디어를 제공했습니다.
또 다른 옵션-테스트가 실패한 디버거를 시작합니다.
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)
제가 사용하는 방법은 정말 간단합니다. 경고로 기록하기 만하면 실제로 표시됩니다.
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)
로깅 사용 :
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
.
이를 위해 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)
이 경우 내가하는 일은 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
inspect.trace will let you get local variables after an exception has been thrown. You can then wrap the unit tests with a decorator like the following one to save off those local variables for examination during the post mortem.
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))
The last line will print the returned values where the test succeeded and the local variables, in this case x, when it fails:
{'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 :-)
How about catching the exception that gets generated from the assertion failure? In your catch block you could output the data however you wanted to wherever. Then when you were done you could re-throw the exception. The test runner probably wouldn't know the difference.
Disclaimer: I haven't tried this with python's unit test framework but have with other unit test frameworks.
Admitting that I haven't tried it, the testfixtures' logging feature looks quite useful...
Expanding @F.C. 's answer, this works quite well for me:
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),
참고URL : https://stackoverflow.com/questions/284043/outputting-data-from-unit-test-in-python
'Programming' 카테고리의 다른 글
SQLite에서 "같지 않음"에 대한 구문은 무엇입니까? (0) | 2020.08.09 |
---|---|
단일 Flask 프로세스가 수신하는 동시 요청 수는 얼마입니까? (0) | 2020.08.09 |
서블릿 필터로 요청 매개 변수 수정 (0) | 2020.08.09 |
Java의 가변 길이 (동적) 배열 (0) | 2020.08.09 |
현재 날짜 / 시간을 DD / MM / YYYY HH : MM 형식으로 가져 오려면 어떻게해야합니까? (0) | 2020.08.09 |