Разделы портала

Онлайн-тренинги

.
Создание тестов для REST API на Python с использованием запросов. Часть 2: тесты, управляемые через данные
08.07.2020 00:00

Автор: Баз Дейкстра (Bas Djikstra)
Оригинал статьи
Перевод: Ольга Алифанова

Создание тестов для REST API на Python с использованием запросов. Часть 1: базовые тесты

Недавно я провел свой первый трехдневный курс "Python для тестировщиков". Одна из тем, раскрытых в этом курсе – это создание тестов для REST API с использованием библиотеки запросов Python и фреймворка юнит-тестирования pytest.

В этой короткой серии статей я хочу исследовать библиотеку запросов Python и ее использование для создания тестов REST API. Это вторая часть серии, в которой мы рассмотрим создание тестов, управляемых через данные.

О тестировании, управляемом через данные

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

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

С точки зрения поддерживаемости это, однако, не лучшая идея. Вместо этого лучше написать управляемый через данные тест: он получает свои тестовые данные из источника данных, и проводит несколько итераций согласно строкам (или записям) в этом источнике.

Создание объекта тестовых данных

Большая часть фреймворков для юнит-тестирования поддерживает тестирование, управляемое через данные, и pytest не исключение. Прежде чем разобраться, как создать тест, управляемый через данные, в Python, создадим наш источник тестовых данных. В Python это легко – просто создадим список наборов значений, где каждый набор соответствует одной итерации теста ("тест-кейса", если вам так больше нравится).

  1. test_data_zip_codes = [
  2. ("us", "90210", "Beverly Hills"),
  3. ("ca", "B2A", "North Sydney South Central"),
  4. ("it", "50123", "Firenze")
  5. ]

Мы будем прогонять три итерации одного и того же теста – получать данные о геолокации для заданного кода страны и индекса (первые два элемента в наборе), а затем убеждаться, что соответствующее название места, возвращенное API, соответствует ожидаемому (третий элемент в наборе).

Создание теста, управляемого через данные, в pytest

Теперь, когда у нас есть тест-данные, давайте превратим существующий тест из первой части цикла статей в тест, управляемый через данные.

  1. @pytest.mark.parametrize("country_code, zip_code, expected_place_name", test_data_zip_codes)
  2. def test_using_test_data_object_get_location_data_check_place_name(country_code, zip_code, expected_place_name):
  3. response = requests.get(f"http://api.zippopotam.us/{country_code}/{zip_code}")
  4. response_body = response.json()
  5. assert response_body["places"][0]["place name"] == expected_place_name

Pytest поддерживает тестирование через данные путем встроенного маркера @pytest.mark.parametrize. Этот маркер принимает два аргумента – первый говорит pytest, как (в каком порядке) присваивать элементы набора из источника данных аргументам тестового метода, а второй аргумент – это сам объект тестовых данных.

Тест-методы, которые мы рассматривали в прошлый раз, аргументов не имели, но так как мы скармливаем тестам данные извне, нам нужно определить три аргумента для тест-метода: код страны, индекс, и ожидаемое название места. Затем эти аргументы можно использовать в теле тест-метода – первые два как значения параметра пути в вызове API, а последний – как значение ожидаемого результата, извлеченного из JSON-ответа.

Запуск теста

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

С моей точки зрения, это демонстрация мощи тестирования через данные. Мы можем провести столько итераций, сколько требуется для конкретного теста, и не дублировать код – надо только сообщить pytest, откуда брать тестовые данные. Нужна дополнительная итерация с другими значениями? Просто добавьте запись в объект тестовых данных. Хотите обновить или убрать тест-кейс? Вы знаете, что делать.

Другая полезная штука с тестированием через данные при помощи pytest: когда падает одна из итераций, pytest сообщит, какая именно упала, и какие конкретно значения использовались.

Создание внешнего источника данных

В этом примере тестовые данные все еще были жестко прописаны в коде теста. Это может быть неоптимальным способом работы. Что, если бы мы могли определить тестовые данные во внешнем источнике данных и приказать pytest считывать их оттуда?

Для примера создадим .csv-файл, содержащий те же данные, что и объект тест-данных, который мы видели ранее:

country_code,zip_code,expected_place_name

us,90210,Beverly Hills

ca,B2A,North Sydney South Central

it,50123,Firenze

Для использования этих данных в тесте нужно написать метод Python, считывающий данные из файла и возвращающий их в совместимом с маркером parametrize формате. Python хорошо поддерживает работу с .csv-файлами благодаря встроенной библиотеке csv:

  1. import csv
  2. def read_test_data_from_csv():
  3. test_data = []
  4. with open('test_data/test_data_zip_codes.csv', newline='') as csvfile:
  5. data = csv.reader(csvfile, delimiter=',')
  6. next(data) # skip header row
  7. for row in data:
  8. test_data.append(row)
  9. return test_data

Этот метод открывает .csv-файл в режиме чтения, пропускает строку заголовков, добавляет все прочие строки в список значений тестовых данных test_data по одному, и возвращает объект тестовых данных.

Тест-метод теперь требует обновления, дабы не использовать жестко закодированные данные, забирая вместо этого результат работы метода, читающего данные из csv-файла:

  1. @pytest.mark.parametrize("country_code, zip_code, expected_place_name", read_test_data_from_csv())
  2. def test_using_csv_get_location_data_check_place_name(country_code, zip_code, expected_place_name):
  3. response = requests.get(f"http://api.zippopotam.us/{country_code}/{zip_code}")
  4. response_body = response.json()
  5. assert response_body["places"][0]["place name"] == expected_place_name

Запуск обновленного теста покажет, что и в этом случае прогоняется три итерации теста. Конечно, вы можете использовать и другие источники, не только csv – например, результат запроса к базе данных, XML или JSON. Если вы способны написать метод, возвращающий список наборов тестовых значений – все в ваших руках.

В следующий раз мы продолжим исследование работы с JSON и XML в запросах API и теле ответов.

Самостоятельное использование примеров

Примеры кода из этой статьи можно найти на моей странице GitHub. Если вы скачали проект и (при условии правильной установки Python) запустили

pip install -r requirements.txt

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

Обсудить в форуме