[Тестирование IT-систем, Python, Тестирование мобильных приложений] Передача динамических объектов от setup к тестовой функции в py.test

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
20-Авг-2020 17:34

В больших проектах в какой-то момент получается ситуация, когда тестов на проекте уже много и параллельно развивается собственный высокоуровневый фреймворк. Фреймворк, в данном случае, как обертка над функциями объекта тестирования и возможностями различных инструментов которые используются на проекте. Кроме того все папки заполнены фикстурами, многие из которых используются только в одном тестовом файле.
В этот прекрасный момент возникают некоторые проблемы. Про одну из них я уже писал, это реализация удобной параметризации, например из файла. Про следующую, из наиболее злосчастных, поговорим в этой статье.
«Капитан, у нас много фикстур и появляются глобалы»
Перегрузка тестовых директорий фикстурами это вполне логичное следствие использования концепта который заложен в py.test, но иногда такой подход переходит за рамки приемлемого. Кроме того, мы часто можем наблюдать в тестах конструкции, которые призваны определить какую информацию надо взять тесту в отдельном определенном случае или желание изначально проверить возможность дальнейшего прохождения теста еще на стадии подготовки среды.
Самое большое заблуждение, в ситуации когда эти конструкции передаются из некой setup фикстуры и идут вдоль всего теста, — использовать global переменные. Я столкнулся с подобными тяжелыми случаями и эта идея приходит на ум одной из первых.
В этом месте стоит упомянуть, что концепция фикстур позволяет избежать такой порчи чистоты кода, но также даёт ещё дополнительные уровни абстракции и множество референсов. На крайний случай можно завести ужасную привычку распаковывать результат фикстуры, но в таком случае мы портим логи, т.к. у нас нет разделения на Setup, Run и Teardown, и дополнительно усложняем код в момент распаковки результатов или плодим множественные шорткаты.
Рассмотрим на примерах, и начнем с ужасного:
"Фикстуры и global"
import pytest
@pytest.fixture(autouse=True)
def setup(create_human, goto_room, goto_default_position, choose_window, get_current_view):
    global human
    global window
    # Сделаем подготовительные действия
    desired_room = 1 # Можем этот момент параметризировать, в зависимости от задачи
    human = create_human("John", "Doe") # Допускаем что один и тот же человек во всех случаях
  
    # Если что-то пошло не по плану, то дальше нет смысла продолжать
    assert goto_room(human, desired_room), "{} не дошел в комнату {}".format(human.full_name, desired_room)
    
    # Выбираем случайное окно
    window = choose_window(desired_room)
    view = get_current_view(window)
    assert view, "В окне {} ничего нет".format (window)
    
    yield
    # В Teardown вернёмся в начальное место
    goto_default_position(human)
@pytest.mark.parametrize(
    "city, expected_result",
    [
        ("New York", False), 
        ("Berlin", False),
        ("Unknown", True)
    ]
)
def test_city_in_window(city, expected_result):
    """Проверим что за вид у нас за окном."""
    window_view = human.look_into(window)
    recognized_city = human.recognize_city(window_view)
    assert (recognized_city == city) == expected_result, "Мы увидели город которого нет"

В результате:
  • Есть первоначальные проверки
  • Есть злосчастный global

"Распаковка во всей красе"
import pytest
@pytest.fixture
def setup(create_human, goto_room, goto_default_position, choose_window, get_current_view):
    # Сделаем подготовительные действия
    desired_room = 1 # Можем этот момент параметризировать, в зависимости от задачи
    human = create_human("John", "Doe") # Допускаем что один и тот же человек во всех случаях
  
    # Если что-то пошло не по плану, то дальше нет смысла продолжать
    assert goto_room(human, desired_room), "{} не дошел в комнату {}".format(human.full_name, desired_room)
    
    # Выбираем случайное окно
    window = choose_window(desired_room)
    view = get_current_view(window)
    assert view, "В окне {} ничего нет".format (window)
    
    yield { "human": human, "window": window}
    # В Teardown вернёмся в начальное место
    goto_default_position(human)
@pytest.mark.parametrize(
    "city, expected_result",
    [
        ("New York", False), 
        ("Berlin", False),
        ("Unknown", True)
    ]
)
def test_city_in_window(setup, city, expected_result):
    """Проверим что за вид у нас за окном."""
    data = setup
    window_view = data["human"].look_into(data["window"])
    recognized_city = data["human"].recognize_city(window_view)
    assert (recognized_city == city) == expected_result, "Мы увидели город которого нет"

В результате:
  • Непонятный шорткат на результат фикстуры setup
  • Распаковка малоинформативная или может занять пару, а то и десяток, строк

Если на маленьком примере это не выглядит так критично, то когда тест разрастается до 400+ строк, это начинает бросаться в глаза.
Маленькая хитрость про классы
Так как же поступить, если мы видим перед собой 8 фикстур в setup из которого нам надо достать: и несколько фикстур после использования для предустановок теста, и данные которые мы обработаем и провалидируем вне рамок нашего кейса?
Это звоночек — настало время взять инстанс класса и передать всё через него. Сам инстанс за нас создаст py.test, поэтому нам надо будет просто указать его атрибуты.
Усовершенствуем наш пример:
import pytest
class TestWindowView:
    @pytest.fixture
    def setup(self, create_human, goto_room, goto_default_position, choose_window, get_current_view):
        # Сделаем подготовительные действия
        desired_room = 1 # Можем этот момент параметризировать, в зависимости от задачи
        self.human = create_human("John", "Doe") # Допускаем что один и тот же человек во всех случаях
  
        # Если что-то пошло не по плану, то дальше нет смысла продолжать
        assert goto_room(self.human, desired_room), "{} не дошел в комнату {}".format(human.full_name, desired_room)
    
        # Выбираем случайное окно
        self.window = choose_window(desired_room)
        view = get_current_view(self.window)
        assert view, "В окне {} ничего нет".format (self.window)
    
        yield
        # В Teardown вернёмся в начальное место
        goto_default_position(self.human)
    @pytest.mark.parametrize(
        "city, expected_result",
        [
            ("New York", False), 
            ("Berlin", False),
            ("Unknown", True)
        ]
    )
    def test_city_in_window(self, setup, city, expected_result):
        """Проверим что за вид у нас за окном."""
        window_view = self.human.look_into(self.window)
        recognized_city = self.human.recognize_city(window_view)
        assert (recognized_city == city) == expected_result, "Мы увидели город которого нет"

В результате:
  • Чистая и внятная передача нужных объектов
  • Никаких распаковок и global

Про целесообразность метода
С точки зрения целостности взятый пример кажется невнятными и полноценно заменяемым другими подходами, но в данном случае этот пример стоит учитывать только как демонстрацию подобной возможности и возможность оценить лаконичность итогового кода.
По сути же, речь идёт сугубо о динамических объектах для последующего тестирования. Это также касается удобных шорткатов для дальнейшего использования в тех случаях, когда состояние среды не зависит от входных параметров и во многом зависит от внешних факторов.
На мой взгляд наиболее подходящими сюжетами использования описанного подхода является работа с Android/iOS приложениями через Appium и тестирование IOT/Embedded устройств.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_testirovanie_itsistem (Тестирование IT-систем), #_python, #_testirovanie_mobilnyh_prilozhenij (Тестирование мобильных приложений), #_python, #_pytest, #_appium, #_quality_assurance, #_testing, #_test_automation, #_testirovanie_itsistem (
Тестирование IT-систем
)
, #_python, #_testirovanie_mobilnyh_prilozhenij (
Тестирование мобильных приложений
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 23:53
Часовой пояс: UTC + 5