Python · Тестирование с pytest · Продвинутый
Интеграционное тестирование
Тестируйте несколько компонентов вместе во взаимодействии с реальными сервисами.
Краткое введение в тему и пояснения перед упражнениями (упражнения ниже):
Интеграционные тесты на практике: потоки аутентификации и общие сессии
#Справочная карточка: интеграционное тестирование
#Упражнения:
Интеграционный поток регистрации и входа
#Напишите интеграционный тест `test_register_and_login`, который: 1. Генерирует уникальное имя пользователя через `f'pytest_{int(time.time())}'` 2. Регистрирует пользователя через `POST /api/users/register/` с полями: `username`, `email`, `password`, `first_name`, `last_name` 3. Проверяет, что ответ регистрации равен 200 или 201 4. Выполняет вход через `POST /api/auth/token/` с новыми учётными данными 5. Получает `/api/users/profile/` с возвращённым токеном 6. Проверяет, что `username` профиля совпадает с зарегистрированным Используйте `BASE_URL = 'https://apilearn.tukas.dev'`.
# test_register_flow.py
import time
import requests
BASE_URL = 'https://apilearn.tukas.dev'
def test_register_and_login():
username = f'pytest_{int(time.time())}'
# Шаг 1: регистрация
reg = requests.post(f'{BASE_URL}/api/users/register/', json={
'username': username,
'email': f'{username}@example.com',
'password': ...,
'first_name': 'Test',
'last_name': 'User',
})
assert reg.status_code in (200, 201)
# Шаг 2: вход
auth = requests.post(f'{BASE_URL}/api/auth/token/', json={
'username': username,
'password': ...,
})
assert auth.status_code == 200
token = ...
# Шаг 3: получить профиль
profile = requests.get(
f'{BASE_URL}/api/users/profile/',
headers={'Authorization': f'Token {token}'},
)
assert profile.status_code == 200
assert ...
Решение
# test_register_flow.py
import time
import requests
BASE_URL = 'https://apilearn.tukas.dev'
def test_register_and_login():
username = f'pytest_{int(time.time())}'
password = 'Secure123!'
reg = requests.post(f'{BASE_URL}/api/users/register/', json={
'username': username,
'email': f'{username}@example.com',
'password': password,
'first_name': 'Test',
'last_name': 'User',
})
assert reg.status_code in (200, 201)
auth = requests.post(f'{BASE_URL}/api/auth/token/', json={
'username': username,
'password': password,
})
assert auth.status_code == 200
token = auth.json()['token']
profile = requests.get(
f'{BASE_URL}/api/users/profile/',
headers={'Authorization': f'Token {token}'},
)
assert profile.status_code == 200
assert profile.json()['username'] == username
Тестирование ограничений пагинации
#Напишите фикстуру `base_url` уровня session, возвращающую `'https://apilearn.tukas.dev'`, затем напишите три теста: 1. `test_products_returns_results` -- GET `/api/products/`, проверить статус 200, наличие ключа `'results'`, что результат является списком хотя бы с 1 элементом 2. `test_page_size_two` -- GET `/api/products/?page_size=2`, проверить что список результатов содержит **не более** 2 элементов 3. `test_second_page` -- GET `/api/products/?page=2&page_size=2`, проверить статус 200 (вторая страница может быть пустой, но не должна возвращать 404)
# conftest.py
import pytest
@pytest.fixture(scope='session')
def base_url():
return 'https://apilearn.tukas.dev'
# test_pagination.py
import requests
def test_products_returns_results(base_url):
resp = requests.get(f'{base_url}/api/products/')
...
def test_page_size_two(base_url):
resp = requests.get(f'{base_url}/api/products/?page_size=2')
...
def test_second_page(base_url):
resp = requests.get(f'{base_url}/api/products/?page=2&page_size=2')
...
Решение
# conftest.py
import pytest
@pytest.fixture(scope='session')
def base_url():
return 'https://apilearn.tukas.dev'
# test_pagination.py
import requests
def test_products_returns_results(base_url):
resp = requests.get(f'{base_url}/api/products/')
assert resp.status_code == 200
data = resp.json()
assert 'results' in data
assert isinstance(data['results'], list)
assert len(data['results']) > 0
def test_page_size_two(base_url):
resp = requests.get(f'{base_url}/api/products/?page_size=2')
assert resp.status_code == 200
results = resp.json()['results']
assert len(results) <= 2
def test_second_page(base_url):
resp = requests.get(f'{base_url}/api/products/?page=2&page_size=2')
assert resp.status_code == 200
Аутентификация уровня session для нескольких тестов
#Напишите `conftest.py` с двумя фикстурами уровня session: - `base_url` -- возвращает `'https://apilearn.tukas.dev'` - `auth_headers` -- выполняет вход как `testuser` / `TestUser2024!` и возвращает `{'Authorization': 'Token ...'}` Напишите три теста, каждый из которых использует `auth_headers`: 1. `test_profile_username` -- проверить что профиль в ответе имеет `username == 'testuser'` 2. `test_products_with_auth` -- проверить что GET `/api/products/` возвращает 200 (аутентифицированный) 3. `test_echo_with_auth` -- проверить что GET `/api/echo/` возвращает 200
# conftest.py
import pytest
import requests
@pytest.fixture(scope='session')
def base_url():
return 'https://apilearn.tukas.dev'
@pytest.fixture(scope='session')
def auth_headers(base_url):
resp = requests.post(f'{base_url}/api/auth/token/', json={
'username': 'testuser',
'password': ...,
})
resp.raise_for_status()
token = ...
return {'Authorization': f'Token {token}'}
# test_authenticated.py
import requests
def test_profile_username(base_url, auth_headers):
...
def test_products_with_auth(base_url, auth_headers):
...
def test_echo_with_auth(base_url, auth_headers):
...
Решение
# conftest.py
import pytest
import requests
@pytest.fixture(scope='session')
def base_url():
return 'https://apilearn.tukas.dev'
@pytest.fixture(scope='session')
def auth_headers(base_url):
resp = requests.post(f'{base_url}/api/auth/token/', json={
'username': 'testuser',
'password': 'TestUser2024!',
})
resp.raise_for_status()
token = resp.json()['token']
return {'Authorization': f'Token {token}'}
# test_authenticated.py
import requests
def test_profile_username(base_url, auth_headers):
resp = requests.get(f'{base_url}/api/users/profile/', headers=auth_headers)
assert resp.status_code == 200
assert resp.json()['username'] == 'testuser'
def test_products_with_auth(base_url, auth_headers):
resp = requests.get(f'{base_url}/api/products/', headers=auth_headers)
assert resp.status_code == 200
def test_echo_with_auth(base_url, auth_headers):
resp = requests.get(f'{base_url}/api/echo/', headers=auth_headers)
assert resp.status_code == 200
Одна и та же фича: мокированный vs реальный HTTP
#Реализуйте `get_product_count(base_url)` -- функцию, которая делает GET `{base_url}/api/products/` и возвращает целочисленное значение поля `count` из JSON ответа. Напишите два теста для неё: 1. `test_get_product_count_unit` -- используйте `unittest.mock.patch` для мокирования `requests.get`, сделайте так чтобы он возвращал фейковый JSON ответ `{'count': 7, 'results': []}`, и проверьте что `get_product_count(...)` возвращает `7`. Тест **не должен** выполнять реальный сетевой вызов. 2. `test_get_product_count_integration` -- вызовите `get_product_count('https://apilearn.tukas.dev')` по-настоящему, проверьте что результат является целым числом >= 0. Добавьте комментарий в каждый тест, объясняющий что он может и не может обнаружить.
# test_count_comparison.py
import requests
from unittest.mock import patch
def get_product_count(base_url):
resp = requests.get(f'{base_url}/api/products/')
resp.raise_for_status()
return resp.json()['count']
def test_get_product_count_unit():
# Этот тест: быстрый, без сети.
# Не обнаруживает: переименованное поле в реальном API, неверный URL, проблемы с аутентификацией.
with patch('requests.get') as mock_get:
mock_get.return_value.status_code = 200
mock_get.return_value.raise_for_status.return_value = None
mock_get.return_value.json.return_value = ...
result = get_product_count('http://fake')
assert result == ...
def test_get_product_count_integration():
# Этот тест: медленнее, реальная сеть.
# Не обнаруживает: логические баги внутри get_product_count (покрывается юнит-тестом).
result = get_product_count('https://apilearn.tukas.dev')
assert ...
Решение
# test_count_comparison.py
import requests
from unittest.mock import patch
def get_product_count(base_url):
resp = requests.get(f'{base_url}/api/products/')
resp.raise_for_status()
return resp.json()['count']
def test_get_product_count_unit():
# Этот тест: быстрый, без сети.
# Не обнаруживает: переименованное поле в реальном API, неверный URL, проблемы с аутентификацией.
with patch('requests.get') as mock_get:
mock_get.return_value.status_code = 200
mock_get.return_value.raise_for_status.return_value = None
mock_get.return_value.json.return_value = {'count': 7, 'results': []}
result = get_product_count('http://fake')
assert result == 7
def test_get_product_count_integration():
# Этот тест: медленнее, реальная сеть.
# Не обнаруживает: логические баги внутри get_product_count (покрывается юнит-тестом).
result = get_product_count('https://apilearn.tukas.dev')
assert isinstance(result, int)
assert result >= 0
Фикстура уровня session, используемая тремя независимыми тестами
#Создайте фикстуру `product_list` уровня session, которая: - Делает GET `https://apilearn.tukas.dev/api/products/` - Проверяет статус 200 - Возвращает разобранный список `results` Напишите три независимых теста, каждый из которых получает `product_list` как параметр фикстуры: 1. `test_products_not_empty` -- проверить что список содержит хотя бы 1 элемент 2. `test_products_have_slug` -- проверить что каждый продукт имеет непустое поле `slug` 3. `test_products_have_name` -- проверить что каждый продукт имеет непустое поле `name` HTTP-вызов должен происходить **один раз** независимо от того, сколько тестов используют фикстуру.
# conftest.py
import pytest
import requests
@pytest.fixture(scope='session')
def product_list():
resp = requests.get('https://apilearn.tukas.dev/api/products/')
assert resp.status_code == 200
return resp.json()['results']
# test_product_list.py
def test_products_not_empty(product_list):
assert len(product_list) > 0
def test_products_have_slug(product_list):
...
def test_products_have_name(product_list):
...
Решение
# conftest.py
import pytest
import requests
@pytest.fixture(scope='session')
def product_list():
resp = requests.get('https://apilearn.tukas.dev/api/products/')
assert resp.status_code == 200
return resp.json()['results']
# test_product_list.py
def test_products_not_empty(product_list):
assert len(product_list) > 0
def test_products_have_slug(product_list):
assert all(isinstance(p.get('slug'), str) and len(p['slug']) > 0
for p in product_list)
def test_products_have_name(product_list):
assert all(isinstance(p.get('name'), str) and len(p['name']) > 0
for p in product_list)