Есть ли веская причина для перехвата исключений в единичных транзакциях?

#python #django #unit-testing

#python #django #модульное тестирование

Вопрос:

Модуль unittest чрезвычайно хорош для обнаружения проблем в коде. Я понимаю идею изоляции и тестирования частей кода с помощью утверждений:

self.assertEqual(web_page_view.func, web_page_url)

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

Мне интересно, следует ли учитывать ручную обработку исключений когда-либо внутри методов подкласса TestCase.

Потому что, если я оборачиваю блок в try-catch, если что-то не удается, тест возвращает OK и не завершается сбоем:

     def test_simulate_requests(self):
        """
        Simulate requests to a url
        """
        try:
           response = self.client.get('/adress/of/page/')
           self.assertEqual(response.status_code, 200)
        except Exception as e:
            print("error: ", e)
  

Следует ли всегда избегать обработки исключений в таких тестах?

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

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

Ответ №1:

Первая часть ответа:

Как вы правильно говорите, перед фактическим тестированием должна быть некоторая логика. Код, принадлежащий модульному тестированию, может быть разделен на четыре части (я использую терминологию Месароша в следующем): настройка, упражнение, проверка, демонтаж. Часто код тестового примера структурирован таким образом, что код для четырех частей четко разделен и находится в этом точном порядке — это называется четырехфазным шаблоном тестирования.

Фаза упражнения — это сердце теста, где выполняется функциональность, которая должна быть проверена в тесте. Настройка гарантирует, что это происходит в четко определенном контексте. Итак, то, что вы описали в этой терминологии, представляет собой ситуацию, когда во время установки что-то выходит из строя. Это означает, что не выполняются предварительные условия, которые требуются для осмысленного выполнения функциональности, подлежащей тестированию.

Это обычная ситуация, и это означает, что вам на самом деле нужно уметь различать три результата теста: тест может пройти успешно, он может потерпеть неудачу или он может просто быть бессмысленным.

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

 import unittest
class TestException(unittest.TestCase):
   def test_skipTest_shallSkip(self):
      self.skipTest("Skipped because skipping shall be demonstrated.")
  

Вторая часть ответа:

Похоже, что в вашем тесте есть некоторые недетерминированные элементы. self.client.get Может генерировать исключения (но только иногда — иногда это не так). Это означает, что у вас нет контекста во время выполнения теста под контролем. В модульном тестировании это ситуация, которой вы должны стараться избегать. Ваши тесты должны иметь детерминированное поведение.

Один из типичных способов добиться этого — изолировать ваш код от компонентов, которые отвечают за недетерминированность, и во время тестирования заменять эти компоненты mocks. Поведение макетов полностью контролируется тестовым кодом. Таким образом, если ваш код использует какой-либо компонент для доступа к сети, вы бы издевались над этим компонентом. Затем в некоторых тестовых примерах вы можете указать макету имитировать успешное сетевое взаимодействие, чтобы увидеть, как ваш компонент справляется с этим, а в других тестах вы указываете макету имитировать сбой сети, чтобы увидеть, как ваш компонент справляется с этой ситуацией.

Ответ №2:

Есть два «плохих» состояния теста: сбой (когда одно из утверждений завершается неудачей) и Ошибка (когда сам тест завершается неудачей — ваш случай).

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

Если вам нужно подтвердить, что какой-либо протестированный код вызывает исключение, вы должны использовать with self.assertRaises(ExpectedError)

Если какой-то код внутри теста вызывает исключение — лучше узнать это по результату «Ошибка», чем увидеть «ОК, все тесты пройдены»

Если ваша тестовая логика действительно предполагает, что что-то может не сработать в самом тесте, и это нормальное поведение — вероятно, тест неверен. Возможно, вам следует использовать mocks (https://docs.python.org/3/library/unittest.mock.html ) для имитации вызова api или чего-то еще.

В вашем случае, даже если тест завершается неудачей, вы перехватываете его с помощью простого except и говорите «Ок, продолжайте». В любом случае реализация неправильная.

Наконец: нет, не должно быть, кроме как в ваших тестовых примерах

P.S. лучше вызывать ваши тестовые функции с помощью test_what_you_want_to_test_name , в этом случае, вероятно, test_successful_request будет в порядке.