Xuất dữ liệu từ kiểm tra đơn vị trong python


115

Nếu tôi đang viết các bài kiểm tra đơn vị trong python (bằng cách sử dụng mô-đun đơn nhất), liệu có thể xuất dữ liệu từ một bài kiểm tra không thành công, vì vậy tôi có thể kiểm tra nó để giúp suy ra nguyên nhân gây ra lỗi? Tôi biết về khả năng tạo thông báo tùy chỉnh, có thể mang một số thông tin, nhưng đôi khi bạn có thể xử lý dữ liệu phức tạp hơn, không thể dễ dàng biểu diễn dưới dạng chuỗi.

Ví dụ: giả sử bạn có một lớp Foo và đang kiểm tra một thanh phương thức, sử dụng dữ liệu từ danh sách được gọi là testdata:

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

Nếu kiểm tra không thành công, tôi có thể muốn xuất t1, t2 và / hoặc f, để xem tại sao dữ liệu cụ thể này dẫn đến lỗi. Theo đầu ra, ý tôi là các biến có thể được truy cập giống như bất kỳ biến nào khác, sau khi chạy thử nghiệm.

Câu trả lời:


73

Câu trả lời rất muộn cho một người, như tôi, đến đây để tìm kiếm một câu trả lời đơn giản và nhanh chóng.

Trong Python 2.7, bạn có thể sử dụng một tham số bổ sung msgđể thêm thông tin vào thông báo lỗi như sau:

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

Tài liệu ngoại tuyến tại đây


1
Hoạt động trên Python 3.
MrDBA

18
Tài liệu gợi ý về điều này nhưng điều đáng nói là rõ ràng: theo mặc định, nếu msgđược sử dụng, nó sẽ thay thế thông báo lỗi bình thường. Để msgnối vào thông báo lỗi thông thường, bạn cũng cần phải thiết lập TestCase.longMessage True
Catalin Iacob

1
thật tốt khi biết chúng tôi có thể chuyển một thông báo lỗi tùy chỉnh, nhưng tôi muốn in một số thông báo bất kể lỗi.
Harry Moreno

5
Nhận xét của @CatalinIacob áp dụng cho Python 2.x. Trong Python 3.x, TestCase.longMessage mặc định là True.
ndmeiri

70

Chúng tôi sử dụng mô-đun ghi nhật ký cho việc này.

Ví dụ:

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

Điều đó cho phép chúng tôi bật gỡ lỗi cho các bài kiểm tra cụ thể mà chúng tôi biết là không thành công và chúng tôi muốn có thêm thông tin gỡ lỗi.

Tuy nhiên, phương pháp ưa thích của tôi không phải dành nhiều thời gian cho việc gỡ lỗi, mà dành nó để viết các bài kiểm tra chi tiết hơn để phơi bày vấn đề.


Điều gì sẽ xảy ra nếu tôi gọi một phương thức foo bên trong testSomething và nó ghi lại thứ gì đó. Làm cách nào tôi có thể xem kết quả đầu ra mà không cần chuyển trình ghi nhật ký đến foo?
simao

@simao: là foogì? Một chức năng riêng biệt? Một hàm phương thức của SomeTest? Trong trường hợp đầu tiên, một hàm có thể có trình ghi nhật ký của riêng nó. Trong trường hợp thứ hai, hàm phương thức khác có thể có trình ghi nhật ký riêng. Bạn có biết logginggói hoạt động như thế nào không? Nhiều người khai thác là tiêu chuẩn.
S.Lott

8
Tôi thiết lập ghi nhật ký theo cách bạn chỉ định. Tôi cho rằng nó đang hoạt động, nhưng tôi thấy đầu ra ở đâu? Nó không xuất ra bảng điều khiển. Tôi đã thử định cấu hình nó bằng cách ghi vào một tệp, nhưng điều đó cũng không tạo ra bất kỳ đầu ra nào.
MikeyE

"Tuy nhiên, phương pháp ưa thích của tôi không phải dành nhiều thời gian cho việc gỡ lỗi, mà dành nó để viết các bài kiểm tra chi tiết hơn để phơi bày vấn đề." -- nói hay lắm!
Seth

34

Bạn có thể sử dụng các câu lệnh in đơn giản hoặc bất kỳ cách viết nào khác cho stdout. Bạn cũng có thể gọi trình gỡ lỗi Python ở bất kỳ đâu trong các thử nghiệm của mình.

Nếu bạn sử dụng mũi để chạy các bài kiểm tra của mình (mà tôi khuyên bạn nên sử dụng), nó sẽ thu thập lỗi cho mỗi bài kiểm tra và chỉ hiển thị cho bạn nếu bài kiểm tra không thành công, vì vậy bạn không phải sống với đầu ra lộn xộn khi các bài kiểm tra trôi qua.

Mũi cũng có các công tắc để tự động hiển thị các biến được đề cập trong các xác nhận hoặc để gọi trình gỡ lỗi trong các thử nghiệm không thành công. Ví dụ -s( --nocapture) ngăn chặn việc bắt giữ stdout.


Thật không may, mũi dường như không thu thập nhật ký được viết cho stdout / err bằng cách sử dụng khung ghi nhật ký. Tôi có printlog.debug()bên cạnh nhau, và rõ ràng bật DEBUGghi nhật ký ở gốc từ setUp()phương thức, nhưng chỉ printxuất hiện đầu ra.
haridsv

7
nosetests -shiển thị nội dung của stdout cho dù có lỗi hay không - điều gì đó tôi thấy hữu ích.
hargriffle

Tôi không thể tìm thấy công tắc để tự động hiển thị các biến trong tài liệu mũi. Bạn có thể chỉ cho tôi một cái gì đó mô tả họ?
ABM

Tôi không biết có cách nào để tự động hiển thị các biến từ mũi hoặc thấp nhất. Tôi in những thứ tôi muốn xem trong các bài kiểm tra của mình.
Ned Batchelder

16

Tôi không nghĩ đây là những gì bạn đang tìm kiếm, không có cách nào để hiển thị các giá trị biến không bị lỗi, nhưng điều này có thể giúp bạn tiến gần hơn đến việc xuất kết quả theo cách bạn muốn.

Bạn có thể sử dụng đối tượng TestResult do TestRunner.run () trả về để phân tích và xử lý kết quả. Đặc biệt, TestResult.errors và TestResult.failures

Giới thiệu về Đối tượng TestResults:

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

Và một số mã để chỉ bạn đi đúng hướng:

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

Một tùy chọn khác - khởi động trình gỡ lỗi khi kiểm tra không thành công.

Hãy thử chạy các bài kiểm tra của bạn với Testoob (nó sẽ chạy bộ phần mềm độc nhất của bạn mà không có thay đổi) và bạn có thể sử dụng công tắc dòng lệnh '--debug' để mở trình gỡ lỗi khi kiểm tra không thành công.

Đây là một phiên đầu cuối trên 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 ( mũi.readthedocs.org/en/latest/index.html ) là một khuôn khổ khác cung cấp các tùy chọn 'bắt đầu một phiên trình gỡ lỗi'. Tôi chạy nó với '-sx --pdb --pdb-fail', nó không ăn đầu ra, dừng lại sau lần thất bại đầu tiên và rơi vào pdb khi ngoại lệ và lỗi thử nghiệm. Điều này đã loại bỏ nhu cầu của tôi về các thông báo lỗi phong phú, trừ khi tôi lười biếng và đang kiểm tra lặp lại.
jwhitlock

5

Phương pháp tôi sử dụng thực sự đơn giản. Tôi chỉ ghi lại nó như một cảnh báo để nó thực sự hiển thị.

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)

Điều này sẽ hoạt động nếu thử nghiệm thành công? Trong trường hợp của tôi cảnh báo chỉ hiển thị nếu thử nghiệm thất bại
Shreya Maria

@ShreyaMaria vâng nó sẽ
Orane

5

Tôi nghĩ rằng tôi có thể đã suy nghĩ quá mức về điều này. Một cách mà tôi đã nghĩ ra để thực hiện công việc, chỉ đơn giản là có một biến toàn cục, tích lũy dữ liệu chẩn đoán.

Những thứ như thế này:

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

Cảm ơn vì đã trả lời Họ đã cung cấp cho tôi một số ý tưởng thay thế về cách ghi thông tin từ các bài kiểm tra đơn vị trong python.


2

Sử dụng ghi nhật ký:

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

Sử dụng:

# 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

Nếu bạn không thiết lập LOG_FILE, ghi nhật ký sẽ có stderr.


2

Bạn có thể sử dụng loggingmô-đun cho điều đó.

Vì vậy, trong mã kiểm tra đơn vị, hãy sử dụng:

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

Theo mặc định, các cảnh báo và lỗi được xuất ra /dev/stderr, vì vậy chúng sẽ hiển thị trên bảng điều khiển.

Để tùy chỉnh nhật ký (chẳng hạn như định dạng), hãy thử mẫu sau:

# 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

Những gì tôi làm trong những trường hợp này là có một log.debug()số thông báo trong ứng dụng của tôi. Vì mức ghi nhật ký mặc định là WARNING, các thông báo như vậy không hiển thị trong quá trình thực thi bình thường.

Sau đó, trong điều kiện thú vị nhất, tôi thay đổi cấp độ ghi nhật ký thành DEBUG, để các thông báo như vậy được hiển thị trong khi chạy chúng.

import logging

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

Trong các kỳ lân:

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



Xem một ví dụ đầy đủ:

Đây là daikiri.py, một lớp cơ bản thực hiện một Daikiri với tên và giá của nó. Có một phương pháp make_discount()trả lại giá của loại daikiri cụ thể đó sau khi áp dụng mức chiết khấu nhất định:

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

Sau đó, tôi tạo một test_daikiri.pycái mới nhất để kiểm tra việc sử dụng nó:

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

Vì vậy, khi tôi thực thi nó, tôi nhận được log.debugthông báo:

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

OK

1

Kiểm tra.trace sẽ cho phép bạn nhận các biến cục bộ sau khi một ngoại lệ được ném ra. Sau đó, bạn có thể bọc các bài kiểm tra đơn vị bằng một trình trang trí như sau để lưu các biến cục bộ đó để kiểm tra trong quá trình khám nghiệm tử thi.

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

Dòng cuối cùng sẽ in các giá trị được trả về khi kiểm tra thành công và các biến cục bộ, trong trường hợp này là x, khi nó không thành công:

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

Làm thế nào về việc bắt ngoại lệ được tạo ra từ lỗi xác nhận? Trong khối bắt của bạn, bạn có thể xuất dữ liệu theo cách bạn muốn ở bất cứ đâu. Sau đó, khi bạn đã hoàn tất, bạn có thể ném lại ngoại lệ. Người chạy thử có lẽ sẽ không biết sự khác biệt.

Tuyên bố từ chối trách nhiệm: Tôi chưa thử điều này với khung kiểm tra đơn vị của python nhưng đã thử với các khung kiểm tra đơn vị khác.



-1

Mở rộng câu trả lời của @FC, điều này hoạt động khá tốt đối với tôi:

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),
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.