🐛 Типичные ошибки: requests и API

← К оглавлению урока

⚡ Топ-3 ошибки

  • resp.text["key"] — нельзя: text — строка. Используй resp.json()["key"]
  • Нет проверки status_code — тест читает тело ответа при ошибке 500 и падает с непонятным сообщением
  • Хардкод URL в каждом тесте — при смене сервера нужно менять везде

Ошибка 1: resp.text вместо resp.json()

Симптом: TypeError: string indices must be integers при обращении resp.text["name"]
# Плохо: resp.text — строка, нельзя обратиться к полю как к dict
resp = requests.get(BASE_URL + "/company/list")
first_company = resp.text[0]       # ошибка: [0] — первый символ строки, не объект
name = resp.text["name"]           # TypeError
# Хорошо: resp.json() возвращает list или dict
resp = requests.get(BASE_URL + "/company/list")
body = resp.json()                 # list[dict]
first_company = body[0]            # первый элемент списка
name = first_company["name"]       # корректно

Ошибка 2: нет проверки status_code

Симптом: тест падает с KeyError или JSONDecodeError, хотя проблема — 500 Internal Server Error от сервера.
# Плохо: сразу читаем тело без проверки статуса
resp = requests.post(BASE_URL + "/company/create", json={"name": "Test"})
new_id = resp.json()["id"]  # KeyError если сервер вернул 500 с пустым телом
# Хорошо: сначала проверяем статус
resp = requests.post(BASE_URL + "/company/create", json={"name": "Test"})
assert resp.status_code == 201, f"Ожидался 201, получен {resp.status_code}: {resp.text}"
new_id = resp.json()["id"]  # безопасно

Ошибка 3: хардкод URL в каждом тесте

Симптом: при смене сервера нужно исправлять URL в десятках мест.
# Плохо: хардкод в каждом тесте
def test_one():
    requests.get("http://5.101.50.27:8000/company/list")

def test_two():
    requests.post("http://5.101.50.27:8000/auth/login", json={...})
# Хорошо: одна переменная или класс
BASE_URL = "http://5.101.50.27:8000"

def test_one():
    requests.get(BASE_URL + "/company/list")

def test_two():
    requests.post(BASE_URL + "/auth/login", json={...})

Ошибка 4: одинарные кавычки в JSON

Симптом: json.JSONDecodeError: Expecting property name enclosed in double quotes
# Плохо: одинарные кавычки — это Python dict, не JSON
bad_json = "{ 'id': 111, 'name': 'Test' }"
data = json.loads(bad_json)  # JSONDecodeError
# Хорошо: JSON требует двойные кавычки
good_json = '{ "id": 111, "name": "Test" }'
data = json.loads(good_json)  # работает

# Ещё лучше: не писать JSON вручную — используй dict Python, requests сам сериализует
requests.post(url, json={"id": 111, "name": "Test"})

Ошибка 5: хардкод ID компании в тесте

Симптом: тест падает с 404 или данными другой компании — ID поменялся или компании нет.
# Плохо: предполагаем, что компания с ID 7 существует
def test_get_company():
    resp = requests.get(BASE_URL + "/company/7")  # может не быть!
    assert resp.status_code == 200
# Хорошо: создаём компанию, берём ID из ответа
def test_get_company():
    api = CompanyApi(BASE_URL)
    result = api.create_company("Temp", "test")
    company_id = result["id"]                    # динамический ID

    company = api.get_company(company_id)
    assert company["name"] == "Temp"

Ошибка 6: data= вместо json= для JSON-запроса

Симптом: сервер возвращает 422 Unprocessable Entity или 400 Bad Request — данные пришли как form-data, не JSON.
# Плохо: data= передаёт как application/x-www-form-urlencoded
resp = requests.post(url, data={"name": "Test"})  # не JSON!
# Хорошо: json= автоматически устанавливает Content-Type: application/json
resp = requests.post(url, json={"name": "Test"})

Ошибка 7: вызов resp.json() на не-JSON ответе

Симптом: requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1
# Плохо: пытаемся распарсить HTML как JSON
resp = requests.get("https://example.com")
data = resp.json()  # JSONDecodeError — сервер вернул HTML
# Хорошо: сначала проверяем Content-Type или используем resp.text
resp = requests.get("https://example.com")
if "application/json" in resp.headers.get("Content-Type", ""):
    data = resp.json()
else:
    print(resp.text)  # HTML или text

Ошибка 8: assert внутри метода класса с неинформативным сообщением

Симптом: тест падает с AssertionError без объяснения — что именно пошло не так.
# Плохо: нет сообщения об ошибке
def create_company(self, name):
    resp = requests.post(self.url + "/company/create", json={"name": name})
    assert resp.status_code == 201
# Хорошо: информативное сообщение с фактическим статусом
def create_company(self, name):
    resp = requests.post(self.url + "/company/create", json={"name": name})
    assert resp.status_code == 201, \
        f"Ожидался 201, получен {resp.status_code}. Тело: {resp.text}"