Пользовательские исключения в юнит-тестах

Я создал свои собственные исключения как таковые в errors.py

mapper = {
    'E101':
    'There is no data at all for these constraints',
    'E102':
    'There is no data for these constraints in this market, try changing market',
    'E103':
    'There is no data for these constraints during these dates, try changing dates',
}


class DataException(Exception):
    def __init__(self, code):
        super().__init__()
        self.msg = mapper[code]

    def __str__(self):
        return self.msg

Другая функция где-то еще в коде вызывает разные экземпляры DataException, если в кадре данных pandas недостаточно данных. Я хочу использовать unittest, чтобы убедиться, что он возвращает соответствующее исключение с соответствующим сообщением.

На простом примере, почему это не работает:

from .. import DataException
def foobar():
    raise DataException('E101')

import unittest
with unittest.TestCase.assertRaises(DataException):
    foobar()

Как предлагается здесь: Python assertRaises для пользовательских исключений

Я получаю эту ошибку:

TypeError: assertRaises() missing 1 required positional argument: 'expected_exception'

Или альтернативно:

def foobar():
    raise DataException('E101')

import unittest
unittest.TestCase.assertRaises(DataException, foobar)

приводит к:

TypeError: assertRaises() arg 1 must be an exception type or tuple of exception types

Почему он не распознает DataException как Exception? Почему связанный ответ на вопрос stackoverflow работает без указания второго аргумента assertRaises?


person Ludo    schedule 19.03.2018    source источник
comment
Определение конкретных подклассов DataException кажется более питоническим, чем просто обертывание экземпляров вокруг mapper.get. См. различные подклассы OSError в Python 3, которые ранее разные экземпляры OSError, отличающиеся своими атрибутами errno.   -  person chepner    schedule 19.03.2018


Ответы (1)


Вы пытаетесь использовать методы класса TestCase без создания экземпляра; эти методы не предназначены для использования таким образом.

unittest.TestCase.assertRaises — это несвязанный метод. Вы бы использовали его в тестовом методе для определяемого вами класса TestCase:

class DemoTestCase(unittest.TestCase):
    def test_foobar(self):
        with self.assertRaises(DataException):
            foobar()

Ошибка возникает, потому что несвязанные методы не получают self. Поскольку unittest.TestCase.assertRaises ожидает как self, так и второй аргумент с именем expected_exception, вы получаете исключение, поскольку DataException передается в качестве значения для self.

Теперь вам нужно использовать средство запуска тестов для управления вашими тестовыми примерами; Добавить

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

внизу и запустите файл как скрипт. Затем ваши тестовые примеры автоматически обнаруживаются и выполняются.

Технически возможно использовать утверждения вне такой среды, см. Есть ли способ использовать утверждения модульного теста Python вне TestCase?, но вместо этого я рекомендую вам придерживаться создания тестовых случаев.

Чтобы дополнительно проверить коды и сообщение о возникшем исключении, присвойте значение, возвращаемое при входе в контекст, новому имени с помощью with ... as <target>:; объект диспетчера контекста захватывает возникшее исключение, поэтому вы можете делать утверждения о нем:

with self.assertRaises(DataException) as context:
    foobar()

self.assertEqual(context.exception.code, 'E101')
self.assertEqual(
    context.exception.msg,
    'There is no data at all for these constraints')

См. TestCase.assertRaises() документацию.

И последнее, но не менее важное: рассмотрите возможность использования подклассов DataException, а не отдельных кодов ошибок. Таким образом, пользователи вашего API могут просто поймать один из этих подклассов для обработки определенного кода ошибки, вместо того, чтобы выполнять дополнительные тесты для кода и повторно поднимать, если конкретный код не должен был там обрабатываться.

person Martijn Pieters    schedule 19.03.2018
comment
Ах я вижу. Спасибо! И как мне проверить, что возвращается правильное сообщение об ошибке? т.е. проверка того, что данная функция возвращает DataException('E102'), а не DataException('E103') - person Ludo; 19.03.2018
comment
@Ludo: см. assertRaises() документацию; диспетчер контекста сохраняет возбужденное исключение, и вы можете затем делать для него дальнейшие утверждения. - person Martijn Pieters; 19.03.2018