Получение результатов юнит-теста Python в методе tearDown () - PullRequest
61 голосов
/ 11 декабря 2010

Можно ли получить результаты теста (т.е. все ли утверждения прошли) в методе tearDown ()? Я использую сценарии Selenium, и я хотел бы сделать несколько отчетов из tearDown (), однако я не знаю, возможно ли это.

Ответы [ 11 ]

38 голосов
/ 11 декабря 2010

Если вы посмотрите на реализацию unittest.TestCase.run, вы увидите, что все результаты теста собираются в объекте результата (обычно это экземпляр unittest.TestResult), передаваемый в качестве аргумента.В объекте unittest.TestCase не остается никакого статуса результата.

Так что в методе unittest.TestCase.tearDown вы ничего не сможете сделать, если не будете беспощадно нарушать элегантное разделение тестовых случаев и результаты теста с чем-то вроде этого:

import unittest

class MyTest(unittest.TestCase):

    currentResult = None # holds last result object passed to run method

    def setUp(self):
        pass

    def tearDown(self):
        ok = self.currentResult.wasSuccessful()
        errors = self.currentResult.errors
        failures = self.currentResult.failures
        print ' All tests passed so far!' if ok else \
                ' %d errors and %d failures so far' % \
                (len(errors), len(failures))

    def run(self, result=None):
        self.currentResult = result # remember result for use in tearDown
        unittest.TestCase.run(self, result) # call superclass run method

    def test_onePlusOneEqualsTwo(self):
        self.assertTrue(1 + 1 == 2) # succeeds

    def test_onePlusOneEqualsThree(self):
        self.assertTrue(1 + 1 == 3) # fails

    def test_onePlusNoneIsNone(self):
        self.assertTrue(1 + None is None) # raises TypeError

if __name__ == '__main__':
    unittest.main()

РЕДАКТИРОВАТЬ: Это работает для Python 2.6 - 3.3 (изменено для нового Python ниже ).

30 голосов
/ 21 сентября 2016

Это решение предназначено для Python версий 2,7 до 3,7 (самая высокая текущая версия), без каких-либо декораторов или других модификаций в любом коде до tearDown. Все работает по встроенной классификации результатов. Также пропущенные тесты или expectedFailure распознаются правильно. Он оценивает результат текущего теста, а не сводку всех пройденных испытаний. Совместим также с pytest .

import unittest

class MyTest(unittest.TestCase):
    def tearDown(self):
        if hasattr(self, '_outcome'):  # Python 3.4+
            result = self.defaultTestResult()  # these 2 methods have no side effects
            self._feedErrorsToResult(result, self._outcome.errors)
        else:  # Python 3.2 - 3.3 or 3.0 - 3.1 and 2.7
            result = getattr(self, '_outcomeForDoCleanups', self._resultForDoCleanups)
        error = self.list2reason(result.errors)
        failure = self.list2reason(result.failures)
        ok = not error and not failure

        # demo:   report short info immediately (not important)
        if not ok:
            typ, text = ('ERROR', error) if error else ('FAIL', failure)
            msg = [x for x in text.split('\n')[1:] if not x.startswith(' ')][0]
            print("\n%s: %s\n     %s" % (typ, self.id(), msg))

    def list2reason(self, exc_list):
        if exc_list and exc_list[-1][0] is self:
            return exc_list[-1][1]

    # DEMO tests
    def test_success(self):
        self.assertEqual(1, 1)

    def test_fail(self):
        self.assertEqual(2, 1)

    def test_error(self):
        self.assertEqual(1 / 0, 1)

Комментарии: Необходимо сообщить только одно или ноль исключений (ошибка или сбой), потому что до tearDown не следует ожидать большего. Пакет unittest ожидает, что tearDown может вызвать второе исключение. Поэтому списки errors и failures могут содержать только один или ноль элементов вместе до tearDown. Строки после "демо" комментария сообщают о коротком результате.

Демонстрационный вывод: (не важно)

$ python3.5 -m unittest test

EF.
ERROR: test.MyTest.test_error
     ZeroDivisionError: division by zero
FAIL: test.MyTest.test_fail
     AssertionError: 2 != 1

==========================================================
... skipped usual output from unittest with tracebacks ...
...
Ran 3 tests in 0.002s

FAILED (failures=1, errors=1)

Сравнение с другими решениями - (относительно истории фиксации исходного репозитория Python):

  • Это решение использует закрытый атрибут экземпляра TestCase, как и многие другие. другие решения, но я тщательно проверил все соответствующие коммиты в исходном репозитории Python что три альтернативных имени покрывают историю кода начиная с Python От 2,7 до 3,6,2 без пропусков. Это может быть проблемой после какого-то нового Релиз Python, но он может быть четко распознан, пропущен и легко исправлен позже для нового Python. Преимущество заключается в том, что ничего не изменилось раньше при запуске tearDown он никогда не должен нарушать тест и всю функциональность unittest поддерживается, работает с pytest и может работать со многими пакетами расширения, но не с тестом на нос (не удивительно, потому что тест на носитель не совместим, например, с unittest.expectedFailure).

  • Решения с декораторами на пользовательских методах тестирования или с индивидуальным failException ( mgilson , Павел Репин 2-й путь, кенорб) устойчивы к будущий питон версии, но если все должно работать полностью, они будут расти как снежный ком с большим количеством поддерживаемых исключений и более повторяющимися внутренностями юнит-тест. Декорированные функции имеют менее читаемые следы (еще больше уровней добавлено одним декоратором), они более сложны для отладка, и это неприятно, если другой, более важный декоратор есть проблема. (Благодаря mgilson основной функционал готов и известен проблемы могут быть исправлены.)

  • Решение с модифицированным run методом и уловлено result Параметр

    • ( scoffey ) должно работать также для Python 2.6. Интерпретация результатов может быть улучшена до требования вопроса, но ничего не может работать в Python 3.4+, потому что result обновляется после вызова tearDown, никогда раньше.
    • Знак G .: (протестировано на Python 2.7, 3.2, 3.3, 3.4 и с проверкой носа)
  • решение от exc_info() (Павел Репин 2-й способ) работает только с Python 2.

  • Другие решения принципиально похожи, но менее полные или с большим количеством недостатки.


Объясняется исходным хранилищем Python
= Lib / unittest / case.py =
Python v 2.7 - 3.3

class TestCase(object):
    ...
    def run(self, result=None):
        ...
        self._outcomeForDoCleanups = result   # Python 3.2, 3.3
        # self._resultForDoCleanups = result  # Python 2.7
        #                                     # Python 2.6 - no result saved
        ...
        try:
            testMethod()
        except...   # many times for different exception classes
            result.add...(self, sys.exc_info())  # _addSkip, addError, addFailure
        ...
        try:
            self.tearDown()
        ...

Python v. 3.4 - 3.6

    def run(self, result=None):
        ...
        # outocome is a context manager to catch and collect different exceptions
        self._outcome = outcome  
        ...
        with outcome...(self):
            testMethod()
        ...
        with outcome...(self): 
            self.tearDown() 
        ... 
        self._feedErrorsToResult(result, outcome.errors)

Примечание (при чтении сообщений фиксации Python): причина , почему результаты тестов так сильно отделены от тестов, - предотвращение утечек памяти .Любая информация об исключении может получить доступ к кадрам состояния сбоя процесса, включая все локальные переменные.Если фрейм назначен локальной переменной в блоке кода, который также может завершиться с ошибкой, тогда можно легко создать перекрестную ссылку на память.Это не страшно, благодаря сборщику мусора, но свободная память может фрагментироваться быстрее, чем если бы память была освобождена правильно.По этой причине информация об исключении и обратная трассировка очень скоро преобразуются в строки, и поэтому временные объекты, такие как self._outcome, инкапсулированы и имеют значение None в блоке finally, чтобы предотвратить утечки памяти.

14 голосов
/ 11 декабря 2010

ПРЕДУПРЕЖДЕНИЕ: У меня нет возможности дважды проверить следующую теорию в данный момент, находясь вдали от коробки разработчиков.Так что это может быть выстрел в темноте.

Возможно, вы могли бы проверить возвращаемое значение sys.exc_info() в вашем методе tearDown (), если он возвращает (None, None, None), вы знаете, что тестовый пример выполнен успешно.В противном случае вы могли бы использовать возвращенный кортеж для опроса объекта исключения.

См. sys.exc_info документация.

Другой более явный подход - написать декоратор метода, который вы могли быналожите на все ваши методы тестового примера, которые требуют этой специальной обработки.Этот декоратор может перехватывать исключения утверждений и на основе этого изменять некоторое состояние в self, позволяя вашему методу tearDown узнать, что случилось.

@assertion_tracker
def test_foo(self):
    # some test logic
10 голосов
/ 24 марта 2014

Если вы используете Python2, вы можете использовать метод _resultForDoCleanups. Этот метод возвращает объект TextTestResult:

<unittest.runner.TextTestResult run=1 errors=0 failures=0>

Вы можете использовать этот объект для проверки результатов ваших тестов:

def tearDown(self):
    if self._resultForDoCleanups.failures:
        ...
    elif self._resultForDoCleanups.errors:
        ...
    else:
        #Success

Если вы используете Python3, вы можете использовать _outcomeForDoCleanups:

def tearDown(self):
    if not self._outcomeForDoCleanups.success:
        ...
9 голосов
/ 20 апреля 2014

Исходя из ответа amatellanes, если вы используете Python3.4, вы не можете использовать _outcomeForDoCleanups.Вот что мне удалось взломать вместе:

def _test_has_failed(self):
    for method, error in self._outcome.errors:
        if error:
            return True
    return False

хрен, но, похоже, работает.

8 голосов
/ 17 мая 2015

Это зависит от того, какую отчетность вы хотели бы создать.

В случае, если вы хотите выполнить некоторые действия при сбое (например, создание снимков экрана ), вместо использования tearDown() вы можете добиться этого, переопределив failureException.

Например:

@property
def failureException(self):
    class MyFailureException(AssertionError):
        def __init__(self_, *args, **kwargs):
            screenshot_dir = 'reports/screenshots'
            if not os.path.exists(screenshot_dir):
                os.makedirs(screenshot_dir)
            self.driver.save_screenshot('{0}/{1}.png'.format(screenshot_dir, self.id()))
            return super(MyFailureException, self_).__init__(*args, **kwargs)
    MyFailureException.__name__ = AssertionError.__name__
    return MyFailureException
3 голосов
/ 30 августа 2017

Вот решение для тех из нас, кому неудобно использовать решения, основанные на unittest внутренностях:

Сначала мы создадим декоратор, который установит флаг для экземпляра TestCase, чтобы определить, является ли илине тестовый случай не пройден или пройден:

import unittest
import functools

def _tag_error(func):
    """Decorates a unittest test function to add failure information to the TestCase."""

    @functools.wraps(func)
    def decorator(self, *args, **kwargs):
        """Add failure information to `self` when `func` raises an exception."""
        self.test_failed = False
        try:
            func(self, *args, **kwargs)
        except unittest.SkipTest:
            raise
        except Exception:  # pylint: disable=broad-except
            self.test_failed = True
            raise  # re-raise the error with the original traceback.

    return decorator

Этот декоратор на самом деле довольно прост.Он основан на том факте, что unittest обнаруживает неудачные тесты через Exceptions .Насколько мне известно, единственное специальное исключение, которое необходимо обработать, - это unittest.SkipTest (что не означает сбой теста).Все остальные исключения указывают на неудачные тесты, поэтому мы помечаем их как таковые, когда они всплывают перед нами.

Теперь мы можем использовать этот декоратор напрямую:

class MyTest(unittest.TestCase):
    test_failed = False

    def tearDown(self):
        super(MyTest, self).tearDown()
        print(self.test_failed)

    @_tag_error
    def test_something(self):
        self.fail('Bummer')

Это будет действительнораздражает написание этого декоратора все время.Есть ли способ, которым мы можем упростить?Да, есть! * Мы можем написать метакласс для обработки применения декоратора для нас:

class _TestFailedMeta(type):
    """Metaclass to decorate test methods to append error information to the TestCase instance."""
    def __new__(cls, name, bases, dct):
        for name, prop in dct.items():
            # assume that TestLoader.testMethodPrefix hasn't been messed with -- otherwise, we're hosed.
            if name.startswith('test') and callable(prop):
                dct[name] = _tag_error(prop)

        return super(_TestFailedMeta, cls).__new__(cls, name, bases, dct)

Теперь мы применим это к нашему базовому TestCase подклассу, и все готово:

import six  # For python2.x/3.x compatibility

class BaseTestCase(six.with_metaclass(_TestFailedMeta, unittest.TestCase)):
    """Base class for all our other tests.

    We don't really need this, but it demonstrates that the
    metaclass gets applied to all subclasses too.
    """


class MyTest(BaseTestCase):

    def tearDown(self):
        super(MyTest, self).tearDown()
        print(self.test_failed)

    def test_something(self):
        self.fail('Bummer')

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


* Если бы не было более легкого пути, я бы не сделал _tag_error частной функцией; -)

1 голос
/ 04 сентября 2016

Вдохновленный ответом Скофи , я решил поднять mercilessnes на следующий уровень и придумал следующее.

Он работает как в тесте vanilla unittest, так и при запуске через тесты носа, а также работает в Python версий 2.7, 3.2, 3.3 и 3.4 (я специально не тестировал 3.0, 3.1 или 3.5, так как у меня нет они установлены в данный момент, но если я правильно прочитал исходный код , он также должен работать в 3.5):

#! /usr/bin/env python

from __future__ import unicode_literals
import logging
import os
import sys
import unittest


# Log file to see squawks during testing
formatter = logging.Formatter(fmt='%(levelname)-8s %(name)s: %(message)s')
log_file = os.path.splitext(os.path.abspath(__file__))[0] + '.log'
handler = logging.FileHandler(log_file)
handler.setFormatter(formatter)
logging.root.addHandler(handler)
logging.root.setLevel(logging.DEBUG)
log = logging.getLogger(__name__)


PY = tuple(sys.version_info)[:3]


class SmartTestCase(unittest.TestCase):

    """Knows its state (pass/fail/error) by the time its tearDown is called."""

    def run(self, result):
        # Store the result on the class so tearDown can behave appropriately
        self.result = result.result if hasattr(result, 'result') else result
        if PY >= (3, 4, 0):
            self._feedErrorsToResultEarly = self._feedErrorsToResult
            self._feedErrorsToResult = lambda *args, **kwargs: None  # no-op
        super(SmartTestCase, self).run(result)

    @property
    def errored(self):
        if (3, 0, 0) <= PY < (3, 4, 0):
            return bool(self._outcomeForDoCleanups.errors)
        return self.id() in [case.id() for case, _ in self.result.errors]

    @property
    def failed(self):
        if (3, 0, 0) <= PY < (3, 4, 0):
            return bool(self._outcomeForDoCleanups.failures)
        return self.id() in [case.id() for case, _ in self.result.failures]

    @property
    def passed(self):
        return not (self.errored or self.failed)

    def tearDown(self):
        if PY >= (3, 4, 0):
            self._feedErrorsToResultEarly(self.result, self._outcome.errors)


class TestClass(SmartTestCase):

    def test_1(self):
        self.assertTrue(True)

    def test_2(self):
        self.assertFalse(True)

    def test_3(self):
        self.assertFalse(False)

    def test_4(self):
        self.assertTrue(False)

    def test_5(self):
        self.assertHerp('Derp')

    def tearDown(self):
        super(TestClass, self).tearDown()
        log.critical('---- RUNNING {} ... -----'.format(self.id()))
        if self.errored:
            log.critical('----- ERRORED -----')
        elif self.failed:
            log.critical('----- FAILED -----')
        else:
            log.critical('----- PASSED -----')


if __name__ == '__main__':
    unittest.main()

При запуске с unittest:

$ ./test.py -v
test_1 (__main__.TestClass) ... ok
test_2 (__main__.TestClass) ... FAIL
test_3 (__main__.TestClass) ... ok
test_4 (__main__.TestClass) ... FAIL
test_5 (__main__.TestClass) ... ERROR
[…]

$ cat ./test.log
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_1 ... -----
CRITICAL __main__: ----- PASSED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_2 ... -----
CRITICAL __main__: ----- FAILED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_3 ... -----
CRITICAL __main__: ----- PASSED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_4 ... -----
CRITICAL __main__: ----- FAILED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_5 ... -----
CRITICAL __main__: ----- ERRORED -----

При запуске с nosetests:

$ nosetests ./test.py -v
test_1 (test.TestClass) ... ok
test_2 (test.TestClass) ... FAIL
test_3 (test.TestClass) ... ok
test_4 (test.TestClass) ... FAIL
test_5 (test.TestClass) ... ERROR

$ cat ./test.log
CRITICAL test: ---- RUNNING test.TestClass.test_1 ... -----
CRITICAL test: ----- PASSED -----
CRITICAL test: ---- RUNNING test.TestClass.test_2 ... -----
CRITICAL test: ----- FAILED -----
CRITICAL test: ---- RUNNING test.TestClass.test_3 ... -----
CRITICAL test: ----- PASSED -----
CRITICAL test: ---- RUNNING test.TestClass.test_4 ... -----
CRITICAL test: ----- FAILED -----
CRITICAL test: ---- RUNNING test.TestClass.test_5 ... -----
CRITICAL test: ----- ERRORED -----

Фон

Я начал с этим:

class SmartTestCase(unittest.TestCase):

    """Knows its state (pass/fail/error) by the time its tearDown is called."""

    def run(self, result):
        # Store the result on the class so tearDown can behave appropriately
        self.result = result.result if hasattr(result, 'result') else result
        super(SmartTestCase, self).run(result)

    @property
    def errored(self):
        return self.id() in [case.id() for case, _ in self.result.errors]

    @property
    def failed(self):
        return self.id() in [case.id() for case, _ in self.result.failures]

    @property
    def passed(self):
        return not (self.errored or self.failed)

Однако это работает только в Python 2. В Python 3 вплоть до 3.3 включительно поток управления, похоже, немного изменился: пакет unittest для Python 3 обрабатывает результаты после вызывая метод tearDown() каждого теста ... это поведение может быть подтверждено, если мы просто добавим дополнительную строку (или шесть) в наш класс теста:

@@ -63,6 +63,12 @@
             log.critical('----- FAILED -----')
         else:
             log.critical('----- PASSED -----')
+        log.warning(
+            'ERRORS THUS FAR:\n'
+            + '\n'.join(tc.id() for tc, _ in self.result.errors))
+        log.warning(
+            'FAILURES THUS FAR:\n'
+            + '\n'.join(tc.id() for tc, _ in self.result.failures))


 if __name__ == '__main__':

Затем просто повторите тесты:

$ python3.3 ./test.py -v
test_1 (__main__.TestClass) ... ok
test_2 (__main__.TestClass) ... FAIL
test_3 (__main__.TestClass) ... ok
test_4 (__main__.TestClass) ... FAIL
test_5 (__main__.TestClass) ... ERROR
[…]

… и вы увидите, что вы получите в результате:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_1 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_2 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_3 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_4 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_5 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
__main__.TestClass.test_4

Теперь сравните вышеприведенное с выводом Python 2:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_1 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:

CRITICAL __main__: ---- RUNNING __main__.TestClass.test_2 ... -----
CRITICAL __main__: ----- FAILED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_3 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_4 ... -----
CRITICAL __main__: ----- FAILED -----
WARNING  __main__: ERRORS THUS FAR:

WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
__main__.TestClass.test_4
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_5 ... -----
CRITICAL __main__: ----- ERRORED -----
WARNING  __main__: ERRORS THUS FAR:
__main__.TestClass.test_5
WARNING  __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
__main__.TestClass.test_4

Поскольку Python 3 обрабатывает ошибки / сбои после , тест прерывается, мы не можем легко вывести результат теста, используя result.errors или result.failures в каждом случае. (Я думаю, что, вероятно, имеет больше смысла архитектурно обрабатывать результаты теста после срыва его, однако, действительно делает вполне допустимый сценарий использования другого окончания Процедура тестирования в зависимости от статуса теста / неудачи немного сложнее выполнить…)

Таким образом, вместо того, чтобы полагаться на общий result объект, вместо этого мы можем ссылаться на _outcomeForDoCleanups как другие уже упомянутые , которые содержат объект результата для запущенного в данный момент test, и имеет необходимые атрибуты errors и failrues, которые мы можем использовать, чтобы вывести статус теста к моменту вызова tearDown():

@@ -3,6 +3,7 @@
 from __future__ import unicode_literals
 import logging
 import os
+import sys
 import unittest


@@ -16,6 +17,9 @@
 log = logging.getLogger(__name__)


+PY = tuple(sys.version_info)[:3]
+
+
 class SmartTestCase(unittest.TestCase):

     """Knows its state (pass/fail/error) by the time its tearDown is called."""
@@ -27,10 +31,14 @@

     @property
     def errored(self):
+        if PY >= (3, 0, 0):
+            return bool(self._outcomeForDoCleanups.errors)
         return self.id() in [case.id() for case, _ in self.result.errors]

     @property
     def failed(self):
+        if PY >= (3, 0, 0):
+            return bool(self._outcomeForDoCleanups.failures)
         return self.id() in [case.id() for case, _ in self.result.failures]

     @property

Добавлена ​​поддержка ранних версий Python 3.

Начиная с Python 3.4, эта закрытая переменная-член больше не существует , и вместо этого был добавлен новый (хотя и также приватный) метод: _feedErrorsToResult .

Это означает, что для версий 3.4 ( и более поздних ), если потребность достаточно велика, можно - очень хакерски - force можно заставить все это работать снова, как это было в версии 2…

@@ -27,17 +27,20 @@
     def run(self, result):
         # Store the result on the class so tearDown can behave appropriately
         self.result = result.result if hasattr(result, 'result') else result
+        if PY >= (3, 4, 0):
+            self._feedErrorsToResultEarly = self._feedErrorsToResult
+            self._feedErrorsToResult = lambda *args, **kwargs: None  # no-op
         super(SmartTestCase, self).run(result)

     @property
     def errored(self):
-        if PY >= (3, 0, 0):
+        if (3, 0, 0) <= PY < (3, 4, 0):
             return bool(self._outcomeForDoCleanups.errors)
         return self.id() in [case.id() for case, _ in self.result.errors]

     @property
     def failed(self):
-        if PY >= (3, 0, 0):
+        if (3, 0, 0) <= PY < (3, 4, 0):
             return bool(self._outcomeForDoCleanups.failures)
         return self.id() in [case.id() for case, _ in self.result.failures]

@@ -45,6 +48,10 @@
     def passed(self):
         return not (self.errored or self.failed)

+    def tearDown(self):
+        if PY >= (3, 4, 0):
+            self._feedErrorsToResultEarly(self.result, self._outcome.errors)
+

 class TestClass(SmartTestCase):

@@ -64,6 +71,7 @@
         self.assertHerp('Derp')

     def tearDown(self):
+        super(TestClass, self).tearDown()
         log.critical('---- RUNNING {} ... -----'.format(self.id()))
         if self.errored:
             log.critical('----- ERRORED -----')

… при условии, конечно, все потребители этого класса помнят super(…, self).tearDown() в их соответствующих tearDown методах…

Отказ от ответственности: Чисто образовательный, не пытайтесь делать это дома и т. Д. И т. Д. И т. Д. Я не особенно горжусь этим решением, но оно пока работает достаточно хорошо, и это лучшее, что я мог взломать после того, как возился час или два в субботу днем ​​...

1 голос
/ 13 июня 2016

Имя текущего теста можно получить с помощью метода unittest.TestCase.id () .Так что в tearDown вы можете проверить self.id ().

Пример показывает, как:

  • найти, есть ли в текущем тесте ошибка или сбой в списке ошибок или сбоев
  • распечатать идентификатор теста с PASS или FAIL или EXCEPTION

Протестированный пример здесь работает с хорошим примером @scoffey.

def tearDown(self):
    result = "PASS"
    #### find and show result for current test
    # I did not find any nicer/neater way of comparing self.id() with test id stored in errors or failures lists :-7
    id = str(self.id()).split('.')[-1]
    # id() e.g. tup[0]:<__main__.MyTest testMethod=test_onePlusNoneIsNone>
    #           str(tup[0]):"test_onePlusOneEqualsThree (__main__.MyTest)"
    #           str(self.id()) = __main__.MyTest.test_onePlusNoneIsNone
    for tup in self.currentResult.failures:
        if str(tup[0]).startswith(id):
            print ' test %s failure:%s' % (self.id(), tup[1])
            ## DO TEST FAIL ACTION HERE
            result = "FAIL"
    for tup in self.currentResult.errors:
        if str(tup[0]).startswith(id):
            print ' test %s error:%s' % (self.id(), tup[1])
            ## DO TEST EXCEPTION ACTION HERE
            result = "EXCEPTION"

    print "Test:%s Result:%s" % (self.id(), result)

пример результата:

python run_scripts/tut2.py 2>&1 
E test __main__.MyTest.test_onePlusNoneIsNone error:Traceback (most recent call last):
  File "run_scripts/tut2.py", line 80, in test_onePlusNoneIsNone
    self.assertTrue(1 + None is None) # raises TypeError
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

Test:__main__.MyTest.test_onePlusNoneIsNone Result:EXCEPTION
F test __main__.MyTest.test_onePlusOneEqualsThree failure:Traceback (most recent call last):
  File "run_scripts/tut2.py", line 77, in test_onePlusOneEqualsThree
    self.assertTrue(1 + 1 == 3) # fails
AssertionError: False is not true

Test:__main__.MyTest.test_onePlusOneEqualsThree Result:FAIL
Test:__main__.MyTest.test_onePlusOneEqualsTwo Result:PASS
.
======================================================================
ERROR: test_onePlusNoneIsNone (__main__.MyTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "run_scripts/tut2.py", line 80, in test_onePlusNoneIsNone
    self.assertTrue(1 + None is None) # raises TypeError
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

======================================================================
FAIL: test_onePlusOneEqualsThree (__main__.MyTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "run_scripts/tut2.py", line 77, in test_onePlusOneEqualsThree
     self.assertTrue(1 + 1 == 3) # fails
AssertionError: False is not true

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1, errors=1)
1 голос
/ 13 апреля 2016

Python 2.7.

Вы также можете получить результат после unittest.main ():

t = unittest.main(exit=False)
print t.result

или используйте набор:

suite.addTests(tests)
result = unittest.result.TestResult()
suite.run(result)
print result
...