Расширение утверждений

#python #python-unittest

Вопрос:

Я пишу модульные тесты с повторяющимся кодом, которые я хотел бы упростить. В моем тестовом примере выполняется несколько сценариев, в которых ожидается сбой HTTP-вызова с указанным кодом состояния и сообщением об ошибке. Я использую unittest.assertRaises() , вот так:

 with self.assertRaises(MyHTTPClientError) as er:
    await my_http_request()
self.assertEqual(er.exception.code, 404, er.exception)
error_msg = json.loads(er.exception.response.body)["error_description"]
self.assertEqual(error_msg, "Not found")
 

Чтобы сделать код более СУХИМ и удобным для чтения, я хотел бы написать пользовательский метод assertRaisesHTTPError() в своем классе тестового случая, чтобы приведенный выше фрагмент был упрощен до:

 with self.assertRaisesHTTPError(404, "Not found") as er:
    await my_http_request()
 

Я застрял в написании кода. До сих пор у меня это получалось, но я не знаю, как вызвать «тело» оператора with.

 class MyTestCase(unittest.TestCase):

    def assertRaisesHTTPError(self, code: int, error_message: str, *args: Any,
                              **kwargs: Any) -> Any:
        with self.assertRaises(MyHTTPClientError) as er:
            ### What goes here ? ###

        self.assertEqual(er.exception.code, code)
        actual_error_message = json.loads(
            er.exception.response.body)["error_description"]
        self.assertEqual(actual_error_message, error_message)
        return er
 

Комментарии:

1. Это было бы то же самое, что и тело вне метода (т. Е. await my_http_request() ). Есть ли причина, по которой вы можете просто назвать это?

2. my_http_request() это просто держатель места. Обычно для этого требуются аргументы, которые варьируются в зависимости от сценария.

3. можете ли вы просто перейти *args и **kwargs к методу? что-то вроде await my_http_request(*args, **kwargs) ? Если нет, вы можете просто добавить еще два аргумента assertRaisesHTTPError , чтобы принять аргументы метода за вас и передать их таким же образом

4. Иногда необходимы дополнительные вызовы методов. Поэтому в идеале я хотел бы вызвать все тело оператора with, не делая никаких предположений о его содержании.

Ответ №1:

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

 from typing import Callable

class MyTestCase(unittest.TestCase):
    def assertRaisesCustom(self, func: Callable[[], None]) -> Any:
        with self.assertRaises(MyHTTPClientError) as er:
            func()

    def test_something(self):
        def do_something(n: int):
            raise ValueError("something bad happened")
        self.assertRaisesCustom(lambda: do_something(n))