## Від ручного до автоматизованого тестування API
Якщо ви тестували API за допомогою REST-клієнта, ви знаєте цей цикл: надіслати запит, подивитися на відповідь, рухатися далі. Автоматизовані API-тести роблять те саме -- але повторювано, за мілісекунди, і вони повідомляють вас, щойно щось регресує.
З pytest і бібліотекою `requests` API-тест виглядає майже ідентично до ручної перевірки:
```python
import requests
def test_products_list():
resp = requests.get('https://apilearn.tukas.dev/api/products/')
assert resp.status_code == 200
```
Різниця: це запускається автоматично кожного разу, коли ви пушите, без участі людини.
## Що перевіряти в API-тесті
Перевіряйте пошарово -- кожен шар будується на попередньому:
1. **Код статусу** -- чи відповідає взагалі сервер, і чи правильним кодом?
2. **Структура JSON** -- чи є у відповіді очікувані ключі?
3. **Значення** -- чи правильні конкретні значення для цього вхідного запиту?
```python
def test_products_list():
resp = requests.get('https://apilearn.tukas.dev/api/products/')
assert resp.status_code == 200 # шар 1: статус
data = resp.json()
assert 'results' in data # шар 2: структура
assert 'count' in data
assert isinstance(data['results'], list) # шар 2: тип
assert len(data['results']) > 0 # шар 3: значення
```
Починайте з коду статусу. Якщо він неправильний, решта -- шум: тіло відповіді може бути сторінкою помилки, а не JSON.
## BASE_URL: одне місце для змін
Визначте базовий URL API один раз і посилайтеся на нього скрізь. Це робить перемикання між середовищами (staging, local, production) однорядковою зміною:
```python
BASE_URL = 'https://apilearn.tukas.dev'
def test_products():
resp = requests.get(f'{BASE_URL}/api/products/')
assert resp.status_code == 200
```
Ще краще -- помістіть його у `conftest.py` як фікстуру: тоді всі тестові файли спільно його використовують і ви можете перевизначати його на рівні директорії.
## Автентифікація: виносьте у фікстуру
Повторення процесу входу в кожному тесті -- тендітний підхід. Винесіть його у фікстуру:
```python
# conftest.py
import pytest
import requests
BASE_URL = 'https://apilearn.tukas.dev'
@pytest.fixture(scope='session')
def auth_token(base_url):
resp = requests.post(f'{base_url}/api/auth/token/', json={
'username': 'testuser',
'password': 'TestUser2024!',
})
resp.raise_for_status()
return resp.json()['token']
@pytest.fixture
def auth_headers(auth_token):
return {'Authorization': f'Token {auth_token}'}
```
Будь-який тест, що потребує автентифікації, приймає `auth_headers` як параметр -- pytest впроваджує його:
```python
def test_profile(base_url, auth_headers):
resp = requests.get(f'{base_url}/api/users/profile/', headers=auth_headers)
assert resp.status_code == 200
```
Оголошення `auth_token` session-scoped означає, що виклик входу відбувається один раз за запуск тестів, а не один раз за тест.
## Проектування ізольованих, відтворюваних тестів
Хороший API-тест:
- **Не залежить від порядку виконання тестів** -- або читає публічні/стабільні дані, або налаштовує власний стан
- **Не залишає побічних ефектів**, що ламають інші тести -- використовуйте yield-фікстури для очищення всього, що ви створюєте
- **Перевіряє конкретно** -- перевіряйте конкретні значення, а не просто те, що відповідь непорожня
Для read-only ендпоінтів (GET) тести природно ізольовані. Для операцій запису -- очищайте через yield-фікстуру:
```python
@pytest.fixture
def created_cart_item(base_url, auth_headers):
resp = requests.post(
f'{base_url}/api/cart/items/',
json={'product_id': 1, 'quantity': 1},
headers=auth_headers,
)
item_id = resp.json()['id']
yield item_id
requests.delete(f'{base_url}/api/cart/items/{item_id}/', headers=auth_headers)
```
## requests.Response -- що ви отримуєте у відповідь
```python
resp = requests.get(url, headers={}, params={})
resp.status_code # int: 200, 201, 404, 500 тощо
resp.json() # dict або list -- розібране тіло JSON
resp.text # str -- сире тіло відповіді
resp.headers # dict-подібний об'єкт -- заголовки відповіді (ключі без урахування регістру)
resp.url # str -- фінальний URL після будь-яких редиректів
resp.history # list -- проміжні відповіді в ланцюжку редиректів
resp.raise_for_status() # підіймає requests.HTTPError, якщо status_code >= 400
```
Напишіть тест, що викликає `GET https://apilearn.tukas.dev/api/products/` і перевіряє: (1) код статусу 200, (2) JSON-відповідь містить ключ `"results"`, (3) `"results"` є списком, (4) список непорожній. Запустіть `pytest -v` і переконайтеся, що тест проходить.
import requests
BASE_URL = 'https://apilearn.tukas.dev'
def test_products_list():
resp = requests.get(f'{BASE_URL}/api/products/')
# перевірте код статусу 200
# перевірте наявність 'results' у JSON-відповіді
# перевірте, що results є списком
# перевірте, що results непорожній
pass
Рішення
import requests
BASE_URL = 'https://apilearn.tukas.dev'
def test_products_list():
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
Напишіть тест, що викликає GET https://apilearn.tukas.dev/api/products/ з параметрами запиту page=1 і page_size=3. Перевірте, що код статусу відповіді 200 і що len(data["results"]) == 3. Використовуйте аргумент `params` в `requests.get` для передачі параметрів запиту -- не будуйте рядок запиту вручну.
Напишіть conftest.py з: (1) session-scoped фікстурою `auth_token`, що відправляє POST на https://apilearn.tukas.dev/api/auth/token/ з username="testuser" і password="TestUser2024!" та повертає рядок токену; (2) function-scoped фікстурою `auth_headers`, що повертає {"Authorization": "Token <token>"}. Потім напишіть тест, що використовує `auth_headers` для виклику GET https://apilearn.tukas.dev/api/users/profile/ і перевіряє статус 200 та наявність "username" у відповіді.
# conftest.py
import pytest
import requests
BASE_URL = 'https://apilearn.tukas.dev'
@pytest.fixture(scope='session')
def auth_token():
# POST на BASE_URL/api/auth/token/ з dict облікових даних
# поверніть resp.json()['token']
pass
@pytest.fixture
def auth_headers(auth_token):
# поверніть {'Authorization': f'Token {auth_token}'}
pass
# test_profile.py
def test_profile_authenticated(auth_headers):
# GET BASE_URL/api/users/profile/ з auth_headers
# перевірте статус 200
# перевірте наявність 'username' у JSON-відповіді
pass
Напишіть тест, що надсилає GET https://apilearn.tukas.dev/api/echo/ з власним заголовком X-Test-Id: pytest-exercise-4. Перевірте, що код статусу відповіді 200 і що тіло JSON відображає ваш заголовок назад -- ендпоінт echo повертає заголовки запиту під ключем "headers" у JSON-відповіді.
Напишіть два тести для GET https://apilearn.tukas.dev/api/redirect/. Перший: дозвольте requests переходити за редиректом (за замовчуванням) -- перевірте статус 200, що resp.history непорожній, і що фінальний resp.url містить "apilearn.tukas.dev". Другий: передайте allow_redirects=False -- перевірте, що статус є 3xx-кодом (301, 302, 307 або 308) і що у відповіді присутній заголовок "Location".
import requests
BASE_URL = 'https://apilearn.tukas.dev'
def test_redirect_followed():
resp = requests.get(f'{BASE_URL}/api/redirect/')
# перевірте статус 200
# перевірте, що resp.history непорожній
# перевірте наявність 'apilearn.tukas.dev' у resp.url
pass
def test_redirect_not_followed():
resp = requests.get(f'{BASE_URL}/api/redirect/', allow_redirects=False)
# перевірте, що статус є 3xx
# перевірте наявність 'Location' у resp.headers
pass
Ми використовуємо необхідні cookies для роботи сайту. З вашого дозволу ми також можемо зберігати налаштування сайту та використовувати аналітичні й рекламні cookies, щоб розуміти використання сайту й підтримувати розвиток проєкту.
* Ви завжди можете змінити свій вибір у налаштуваннях сайту.
Оберіть категорії cookies
Налаштування аналітики
Можна вимкнути аналітику використання платформи. Також можна надіслати в Google Analytics запит на видалення даних про використання цього сайту, пов'язаних із цим браузером.
Навчальний workspace
Навчайтеся, читаючи, запускаючи код і розв'язуючи задачі.
Практикуйте програмування з поясненнями тем, вправами, інструментами browser IDE, перевіркою regex і тренуванням друку коду в одному workspace.
Відкривайте інструменти у вкладках.Вправи, IDE-інструменти й тренажери залишаються доступними як вкладки сайту.
Перемикайтеся без втрати контексту.Переходьте між поясненнями, кодом та утилітами, зберігаючи своє місце.
Використовуйте sidebar як карту.Ліві панелі містять навігацію, налаштування, файли, libraries та керування інструментами.
PythonJavaScriptSQLite
Одна IDE, три практичні режими
Python у браузері.Запускайте невеликі скрипти, пробуйте бібліотеки й тренуйте API-запити без встановлення.
JavaScript для швидких експериментів.Перевіряйте код для браузера й порівнюйте ідеї поруч із навчальними матеріалами.
SQLite для практики з даними.Відкривайте оглядач бази даних, переглядайте таблиці, пишіть запити й вивчайте SQL локально.
ТемаIDE
Працюйте поруч у split tabs
Тримайте інструкції перед очима.Відкрийте вправу або довідкову сторінку поруч з IDE, замість постійних перемикань.
Порівнюйте інструменти під час навчання.Розміщуйте перевірки regex, пояснення та експерименти з кодом поруч, коли це потрібно для задачі.
Закрийте split, коли завершите.Workspace повернеться до однієї сфокусованої вкладки, а відкриті вкладки сайту залишаться доступними.
Тренажер друку коду
Або звичайного тексту
Тренажер розрахований на фізичну клавіатуру.Відкрийте цей розділ на ноутбуці або комп'ютері з широким екраном. На телефоні тренування друку не працюватиме коректно.
Швидкість: 0 зн/хв
0 слів/хв
Найкраща швидкість (60с): 0 зн/хв
0 слів/хв
Помилки: 0
Загальний час: 0.0 с
Щоб тренувати сліпий друк, не підглядайте на фізичну клавіатуру.