Как избежать комбинаторного взрыва при юнит-тестировании представлений Джанго - PullRequest
0 голосов
/ 04 октября 2018

У меня довольно сложное представление Django (Rest Framework), которое обновляет объект в базе данных.

Чтобы обновить объект, необходимо выполнить некоторые условия (это не реальные условия,но они похожи):

  1. Пользователь должен войти в систему
  2. Имя пользователя должно начинаться с admin
  3. Данные обновления должны бытьдействительный в соответствии с некоторыми правилами
  4. Отдельная дорогая функция is_moon_phase_ok должна возвращать True

Я пытаюсь написать солидный набор модульных тестов для этого представления иСценарии, которые я придумал, следующие:

when not logged in, return 401
when not logged in, return {"fail": "login"}
when not logged in, don't touch database
when not logged in, don't check moon phase
when username is not admin_*, return 401
when username is not admin_*, return {"fail": "username"}
when username is not admin_*, don't touch database
when username is not admin_*, don't check moon phase
when invalid data, return 400
when invalid data, return {"fail": "data"}
when invalid data, don't touch database
when invalid data, don't check moon phase
when logged in, return 200
when logged in, return updated data
when logged in, check moon phase
when logged in, update database

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

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

Есть ли лучший способ выполнить то же самое, с тем же тестовым охватом, но с меньшими затратами труда?

1 Ответ

0 голосов
/ 04 октября 2018

Для меня это звучит очень похоже на параметризованные тесты, у pytest есть поддержка для этого, в основном вы можете написать тест и предоставить входные параметры, как и ожидалось.Таким образом, вы пишете только один тест, но достаточно универсальный для поддержки различных параметров, а значит, меньше кода для поддержки.За сценой pytest запускает один и тот же тест один за другим с вашими заданными параметрами.Написание общих тестов может привести к некоторой логике (как вы можете видеть в моем примере), но вы можете жить с этим, по моему мнению

В качестве общего примера:

@pytest.mark.parametrize('is_admin,expected_status_code,expected_error', [
    (True, 200, {}),
    (False, 401, {"fail": "login"})
])
def test_sample(is_admin, expected_status_code, expected_data):
    # do your setup
    if is_admin:
        user =  create_super_user()
    else:
        user =  normal_user()

    # do your request
    response = client.get('something')

    # make assertion on response
    assert response.status_code == expected_status_code
    assert response.data == expected_data

Вы также можете иметь несколькослои параметров, например ::

@pytest.mark.parametrize('is_admin', [
    True,
    False
])
@pytest.mark.parametrize('some_condition,expected_status_code,expected_error', [
    (True, 200, {}),
    (False, 401, {"fail": "login"})
])

Это выполнит тесты для каждой комбинации is_admin (True / False) и других параметров, хорошо, да?

Проверьте документацию здесь тесты параметризации pytest

Если вы не используете pytest, проверьте эту библиотеку, которая выполняет нечто подобное Параметризованное тестирование с любой структурой тестирования Python

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...