Как может Скрученные Задержанные ошибки без errbacks быть протестированным с пробной версией?

У меня есть некоторый Скрученный код, который создает кратные цепи Deferreds. Некоторые из них могут перестать работать, не имея errback, который откладывает их на цепочке обратного вызова. Я не смог записать модульный тест на этот код - сбой Задержанные причины тест для сбоя после того, как тестовый код завершился. Как я могу записать передающий модульный тест на этот код? Ожидается, что каждый Задержанный, который мог перестать работать в нормальном функционировании, должен иметь errback в конце цепочки, которая откладывает его на цепочке обратного вызова?

То же самое происходит, когда существует неудавшийся Задержанный в DeferredList, если я не создаю DeferredList с consumeErrors. Дело обстоит так, даже когда DeferredList создается с fireOnOneErrback и дан errback, который откладывает его на цепочке обратного вызова. Есть ли какие-либо последствия для consumeErrors помимо подавления отказов при испытании и регистрации ошибок? Должен каждый Задержанный, который может перестать работать без errback быть помещенным DeferredList?

Тесты в качестве примера примера кода:

from twisted.trial import unittest
from twisted.internet import defer

def get_dl(**kwargs):
    "Return a DeferredList with a failure and any kwargs given."
    return defer.DeferredList(
        [defer.succeed(True), defer.fail(ValueError()), defer.succeed(True)],
        **kwargs)

def two_deferreds():
    "Create a failing Deferred, and create and return a succeeding Deferred."
    d = defer.fail(ValueError())
    return defer.succeed(True)


class DeferredChainTest(unittest.TestCase):

    def check_success(self, result):
        "If we're called, we're on the callback chain."        
        self.fail()

    def check_error(self, failure):
        """
        If we're called, we're on the errback chain.
        Return to put us back on the callback chain.
        """
        return True

    def check_error_fail(self, failure):
        """
        If we're called, we're on the errback chain.
        """
        self.fail()        

    # This fails after all callbacks and errbacks have been run, with the
    # ValueError from the failed defer, even though we're
    # not on the errback chain.
    def test_plain(self):
        """
        Test that a DeferredList without arguments is on the callback chain.
        """
        # check_error_fail asserts that we are on the callback chain.
        return get_dl().addErrback(self.check_error_fail)

    # This fails after all callbacks and errbacks have been run, with the
    # ValueError from the failed defer, even though we're
    # not on the errback chain.
    def test_fire(self):
        """
        Test that a DeferredList with fireOnOneErrback errbacks on failure,
        and that an errback puts it back on the callback chain.
        """
        # check_success asserts that we don't callback.
        # check_error_fail asserts that we are on the callback chain.
        return get_dl(fireOnOneErrback=True).addCallbacks(
            self.check_success, self.check_error).addErrback(
            self.check_error_fail)

    # This succeeds.
    def test_consume(self):
        """
        Test that a DeferredList with consumeErrors errbacks on failure,
        and that an errback puts it back on the callback chain.
        """
        # check_error_fail asserts that we are on the callback chain.
        return get_dl(consumeErrors=True).addErrback(self.check_error_fail)

    # This succeeds.
    def test_fire_consume(self):
        """
        Test that a DeferredList with fireOnOneCallback and consumeErrors
        errbacks on failure, and that an errback puts it back on the
        callback chain.
        """
        # check_success asserts that we don't callback.
        # check_error_fail asserts that we are on the callback chain.
        return get_dl(fireOnOneErrback=True, consumeErrors=True).addCallbacks(
            self.check_success, self.check_error).addErrback(
            self.check_error_fail)

    # This fails after all callbacks and errbacks have been run, with the
    # ValueError from the failed defer, even though we're
    # not on the errback chain.
    def test_two_deferreds(self):
        # check_error_fail asserts that we are on the callback chain.        
        return two_deferreds().addErrback(self.check_error_fail)
9
задан Karl Anderson 14 July 2010 в 20:24
поделиться

1 ответ

В суде есть два важных момента, связанных с этим вопросом.

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

Во-вторых, метод тестирования, который возвращает Deferred, не пройдет, если Deferred сработает с ошибкой.

Это означает, что ни один из этих тестов не может пройти:

def test_logit(self):
    defer.fail(Exception("oh no"))

def test_returnit(self):
    return defer.fail(Exception("oh no"))

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

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

Однако есть инструменты для решения каждого из этих случаев. В конце концов, если у вас не может быть прохождения теста для API, который возвращает Deferred, который иногда запускается с Failure, вы никогда не сможете проверить свой код ошибки. Это была бы довольно печальная ситуация. :)

Таким образом, наиболее полезным из двух инструментов для решения этой проблемы является TestCase.assertFailure . Это помощник для тестов, которые хотят вернуть Deferred, который будет срабатывать с ошибкой:

def test_returnit(self):
    d = defer.fail(ValueError("6 is a bad value"))
    return self.assertFailure(d, ValueError)

Этот тест будет пройден, потому что d действительно срабатывает с Failure, завершающим ValueError.Если d сработал с успешным результатом или с ошибкой, обернувшей какой-либо другой тип исключения, то тест все равно завершится неудачно.

Затем есть TestCase.flushLoggedErrors . Это когда вы тестируете API, который должен регистрировать ошибку. В конце концов, иногда вы все же хотите сообщить администратору о проблеме.

def test_logit(self):
    defer.fail(ValueError("6 is a bad value"))
    gc.collect()
    self.assertEquals(self.flushLoggedErrors(ValueError), 1)

Это позволяет вам проверять зарегистрированные сбои, чтобы убедиться, что ваш код регистрации работает правильно. Это также говорит испытанию не беспокоиться о вещах, которые вы смыли, чтобы они больше не приводили к сбою теста. (Вызов gc.collect () присутствует, потому что ошибка не регистрируется до тех пор, пока отложенный объект не будет собран сборщиком мусора. На CPython он будет сразу же собран из-за поведения сборщика мусора с подсчетом ссылок. Однако , в Jython, PyPy или любой другой среде выполнения Python без подсчета ссылок вы не можете полагаться на это.)

Кроме того, поскольку сборка мусора может происходить практически в любое время, иногда вы можете обнаружить, что один из ваших тестов завершается неудачно, потому что ошибка регистрируется отложенным, созданным более ранним тестом, собираемым мусором во время выполнения более позднего теста. Это почти всегда означает, что ваш код обработки ошибок каким-то образом неполон - вам не хватает errback, или вам не удалось связать где-то два Deferred'а вместе, или вы позволяете своему методу тестирования завершиться до того, как задача, которую он начал, на самом деле завершится - но способ сообщения об ошибке иногда затрудняет отслеживание кода нарушения. В этом может помочь опция - force-gc .Это приводит к тому, что испытание вызывает сборщик мусора между каждым тестовым методом. Это значительно замедлит ваши тесты, но должно привести к тому, что ошибка будет регистрироваться в тесте, который на самом деле ее запускает, а не в произвольном более позднем тесте.

15
ответ дан 4 December 2019 в 13:44
поделиться
Другие вопросы по тегам:

Похожие вопросы: