использование assertRaises - обработка распространяемых исключений - PullRequest
2 голосов
/ 21 января 2012

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

Я скорректировал свой тест (см. пример кода ниже).Я хотел бы знать, допустим ли этот тип подхода и позволяет ли какая-либо из платформ Python для тестирования или макета напрямую его реализовать?(в настоящее время я использую unittest и mox)

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

Примечание: пример кода должен потерпеть неудачу, если вы запустите его, чтобы продемонстрировать поведение, которое я хотел бы видеть,Я использую Python 2.7, Mox 0.5.3

import sys
import urllib2
from contextlib import closing

try:
    import lxml.etree as ET
except ImportError:
    import xml.etree.ElementTree as ET


class Defect(Exception):
    """Wrapped exception, for module error detection"""
    def __init__(self, *args):
        Exception.__init__(self, *args)
        self.wrapped_exc = sys.exc_info()


class StudioResources:
    """Dummy class"""
    def _opener(self, request, html=False):
        with closing(urllib2.urlopen(request)) as response:
            try:
                if html:
                    import lxml.html
                    return lxml.html.parse(response)
                else:
                    return ET.parse(response)
            except urllib2.HTTPError, e:
                if e.code in [400, 500]: # Bad Request, Internal Server Error
                    raise Defect, "report error to the library maintainer"
                else:
                    raise


###
# Tests
###
import unittest
import mox
import traceback
import difflib
import urllib
import httplib


def format_expectation(exc_expected=None, exc_instance=None):
    """Synopsis - For exceptions, inspired by _AssertRaisesContext

    try:
        self.assertRaises(myexc, self.studio._opener, None)
    except Exception, e:
        self.fail(format_expectation(exc_expected=myexc, exc_instance=e))
    """
    if not isinstance(exc_expected, type) or exc_instance is None:
        raise ValueError, "check __init__ args"

    differ = difflib.Differ()
    inst_class = exc_instance.__class__
    def fullname(c): return "%s.%s" % (c.__module__, c.__name__)
    diff = differ.compare(
        (fullname(inst_class),), (fullname(exc_expected),))
    _str = ("Unexpected Exception type.  unexpected:-  expected:+\n%s"
        % ("\n".join(diff),))
    return _str


class StudioTest(mox.MoxTestBase):
    def setUp(self):
        mox.MoxTestBase.setUp(self)
        self.studio = StudioResources()

    def test_opener_defect(self):
        f = urllib.addinfourl(urllib2.StringIO('dummy'), None, None)
        RESP_CODE = 501
        self.mox.StubOutWithMock(f, 'read')
        self.mox.StubOutWithMock(urllib2, 'urlopen')
        urllib2.urlopen(mox.IgnoreArg()).AndReturn(f)
        f.read(mox.IgnoreArg()).AndRaise(urllib2.HTTPError(
            'http://c.com', RESP_CODE, httplib.responses[RESP_CODE], "", None))
        self.mox.ReplayAll()
        try:
            with self.assertRaises(Defect) as exc_info:
                self.studio._opener(None)
        except Exception, e:
            traceback.print_exc()
            self.fail(format_expectation(exc_expected=Defect, exc_instance=e))
        # check the response code
        exc, inst, tb = exc_info.exception.wrapped_exc
        self.assertEquals(inst.code, RESP_CODE)
        self.mox.VerifyAll()


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

1 Ответ

1 голос
/ 07 марта 2012

При написании юнит-тестов всегда полезно ограничивать тест одной вещью.Я не вижу ничего плохого в вашем коде, но я бы все обернул в контекстный менеджер.Я использую нос, а не unittest, который обрабатывает любую ошибку AssertionError как сбой (это означает, что нет необходимости вызывать self.fail()), и я написал свой собственный менеджер контекста для обработки этого случая.Вот код, если вас интересует:

class assert_raises:

    def __init__(self, exception):
        self.exception = exception

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        assert exc_type is self.exception, "Got '{}', expected '{}'"\
            .format('None' if exc_type is None else exc_type.__name__,
                    self.exception.__name__)
        return True

А затем используйте его, как в этом примере:

>>> with assert_raised(ValueError):
...    raise ValueError

>>> with assert_raised(ValueError):
...    pass
Traceback (most recent call last):
    ...
AssertionError: Got 'None', expected 'ValueError'

>>> with assert_raised(ValueError):
...     raise TypeError
Traceback (most recent call last):
    ...
AssertionError: Got 'TypeError', expected 'ValueError'

Так как ошибка AssertionError, нос воспринимает это как сбойв любом случае печатает полную трассировку.Это предназначено для носа, но было бы тривиально настроить его на unittest и mox вместо этого.Если вы не слишком обеспокоены точным способом отказа, вы можете использовать его как есть.

...