Вывод данных из юнит-теста в python - PullRequest
104 голосов
/ 12 ноября 2008

Если я пишу юнит-тесты на python (используя модуль 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, чтобы понять, почему именно эти данные привели к ошибке. Под выводом я подразумеваю доступ к переменным, как и к любым другим переменным, после запуска теста.

Ответы [ 14 ]

69 голосов
/ 03 декабря 2012

Очень поздний ответ для человека, который, как и я, приходит сюда в поисках простого и быстрого ответа.

В Python 2.7 вы можете использовать дополнительный параметр msg, чтобы добавить информацию к сообщению об ошибке, например:

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

Официальные документы здесь

66 голосов
/ 12 ноября 2008

Для этого мы используем модуль регистрации.

Например:

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

Это позволяет нам включать отладку для определенных тестов, которые, как мы знаем, не выполняются и для которых нам нужна дополнительная информация об отладке.

Однако мой предпочтительный метод - не тратить много времени на отладку, а тратить его на написание более детальных тестов, чтобы выявить проблему.

31 голосов
/ 12 ноября 2008

Вы можете использовать простые операторы печати или любой другой способ записи в стандартный вывод. Вы также можете вызвать отладчик Python в любом месте ваших тестов.

Если вы используете nose для запуска ваших тестов (что я рекомендую), он будет собирать стандартный вывод для каждого теста и показывать его вам только в случае неудачи теста, так что вам не придется жить с загроможденным выводом при прохождении испытаний.

Нос также имеет переключатели для автоматического отображения переменных, упомянутых в утверждениях, или для вызова отладчика при неудачных тестах. Например, -s (--nocapture) предотвращает захват стандартного вывода.

16 голосов
/ 12 ноября 2008

Я не думаю, что это именно то, что вы ищете, нет способа отобразить значения переменных, которые не дают ошибок, но это может помочь вам приблизиться к выводу результатов так, как вы хотите.

Вы можете использовать объект 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 "<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 голосов
/ 05 марта 2014

Метод, который я использую, очень прост. Я просто регистрирую это как предупреждение, чтобы оно действительно появилось.

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)
5 голосов
/ 14 ноября 2008

Другой вариант - запустить отладчик, если тест не пройден.

Попробуйте запустить свои тесты с Testoob (он запустит ваш unittest suite без изменений), и вы можете использовать ключ командной строки '--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)
5 голосов
/ 12 ноября 2008

Я думаю, я мог обдумать это. Один из способов, с помощью которого я придумываю эту работу - просто иметь глобальную переменную, которая накапливает диагностические данные.

Что-то вроде этого:

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 голосов
/ 23 марта 2016

В этих случаях я делаю log.debug() с некоторыми сообщениями в моем приложении. Поскольку уровень ведения журнала по умолчанию равен WARNING, такие сообщения не отображаются при обычном выполнении.

Затем в unittest я изменяю уровень ведения журнала на DEBUG, чтобы такие сообщения отображались при их запуске.

import logging

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

В юнит-тестах:

# 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, который проверяет его использование:

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
2 голосов
/ 04 мая 2015

Для этого можно использовать модуль 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 голосов
/ 16 февраля 2015

Использовать логирование:

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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...