✅ Решения: ответы на вопросы самопроверки

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

⚡ Ключевые ответы

  • resp.json() — dict/list; resp.text — всегда строка
  • Статус создания — 201 Created
  • CompanyApi нужен для инкапсуляции: тесты не дублируют requests-вызовы
  • Хардкод ID плохо — база может быть пустой; ID берём из ответа на создание

Блок 1: Основы HTTP и requests

Вопрос 1. Клиент-серверная архитектура

Клиент — устройство или программа, которая отправляет запросы: браузер, мобильное приложение, Python-скрипт с requests. Сервер — принимает запросы, обрабатывает и возвращает ответы. Клиентов может быть много, сервер — один. Пример: ваш браузер (клиент) → запрос к google.com → сервер Google → HTML-ответ.

Вопрос 2. HTTP vs HTTPS

HTTP — данные передаются открыто, могут быть перехвачены. HTTPS — добавляет шифрование TLS: данные между клиентом и сервером зашифрованы. HTTPS обязателен при передаче паролей, платёжных данных, персональных данных.

Вопрос 3. HTTP-методы

МетодЧто делаетПример в XClients
GETПолучение данныхGET /company/list
POSTСоздание ресурса / авторизацияPOST /auth/login, POST /company/create
PUTПолное обновлениеПолная замена записи
PATCHЧастичное обновлениеPATCH /company/update/{id}
DELETEУдалениеDELETE /company/{id}

Вопрос 4. Состав запроса и ответа

Запрос: URL, метод, заголовки (Headers), тело (Body — JSON или form-data).
Ответ: статус-код (200, 201, 404…), заголовки, тело (JSON, HTML или пустое).

Блок 2: Библиотека requests

Вопрос 5. Установка

pip install requests
pip show requests   # проверка: версия, путь установки

Вопрос 6. Атрибуты Response

  • resp.status_code — числовой HTTP-статус (200, 404…)
  • resp.json() — парсит JSON → dict/list
  • resp.text — тело ответа как строка (всегда)
  • resp.headers — словарь заголовков ответа
  • resp.ok — True если status_code < 400

Вопрос 7. resp.text vs resp.json()

resp = requests.get("https://jsonplaceholder.typicode.com/posts/1")

print(type(resp.text))    # <class 'str'> — всегда строка
print(type(resp.json()))  # <class 'dict'> — Python-объект

# resp.text нельзя: resp.text["title"] -> TypeError
# resp.json() можно: resp.json()["title"] -> строка

Если ответ не JSON и вызвать resp.json() — будет json.JSONDecodeError.

Вопрос 8. Передача JSON в POST

# Правильно: json= (requests автоматически ставит Content-Type: application/json)
resp = requests.post(url, json={"name": "test"})

# Устаревший способ: data= (передаёт как form-data, не JSON)
# requests.post(url, data={"name": "test"})  -- НЕ используй для JSON API

Блок 3: Работа с JSON

Вопрос 9. json.loads()

import json

# JSON-объект -> dict
obj_str = '{"id": 1, "name": "Test"}'
result = json.loads(obj_str)  # dict: {"id": 1, "name": "Test"}

# JSON-массив -> list
arr_str = '[{"id": 1}, {"id": 2}]'
result = json.loads(arr_str)  # list: [{"id": 1}, {"id": 2}]

Вопрос 10. Частые ошибки JSON + обработка JSONDecodeError

import json

try:
    bad = "{ 'id': 1 }"  # одинарные кавычки — неправильный JSON
    data = json.loads(bad)
except json.JSONDecodeError as e:
    print(f"Ошибка разбора JSON: {e}")

Частые ошибки: одинарные кавычки вместо двойных; лишняя запятая перед }; True/None вместо true/null.

Вопрос 11. JSON vs Python

JSONPython
trueTrue
falseFalse
nullNone
только двойные кавычкиодинарные или двойные

Блок 4: Класс CompanyApi

Вопрос 12. Зачем класс?

Без класса: дублирование requests.get() / requests.post() в каждом тесте; проверка статуса в каждом тесте; при смене URL — ручное исправление везде.

С классом: запросы в одном месте; проверки статуса внутри методов; тест вызывает только api.get_company_list() — не знает деталей HTTP; легко переиспользовать.

Вопрос 13. Методы CompanyApi

  • get_company_list() — GET /company/list → list[dict]
  • get_token(user, password) — POST /auth/login → str (токен)
  • create_company(name, description) — POST /company/create → dict
  • get_company(company_id) — GET /company/{id} → dict
  • edit_company(id, name, descr) — PATCH /company/update/{id} → dict
  • delete_company(company_id) — DELETE /company/{id} → dict
  • set_active_state(id, is_active) — PATCH /company/status_update/{id} → dict

Вопрос 14. Почему нельзя хардкод-ID?

База данных может быть очищена — ID 7 не существует. IDs меняются с каждым запуском тестов. Надёжный тест: создать компанию → взять ID из ответа → использовать этот ID.

Блок 5: Решения практических задач

Задача 1: test_simple_req()

# test_x_clients.py
import requests

BASE_URL = "http://5.101.50.27:8000"

def test_simple_req():
    resp = requests.get(BASE_URL + "/company/list")
    response_body = resp.json()
    first_company = response_body[0]

    assert resp.status_code == 200
    assert resp.headers["Content-Type"] == "application/json"
    assert first_company["name"] == "QA Студия 'ТестировщикЪ'"

Задача 2: test_auth()

# test_x_clients.py
import requests

BASE_URL = "http://5.101.50.27:8000"

def test_auth():
    creds = {
        "username": "harrypotter",
        "password": "expelliarmus"
    }
    resp = requests.post(BASE_URL + "/auth/login", json=creds)
    assert resp.status_code == 200
    assert resp.json()["user_token"] is not None

Задача 3: тест с CompanyApi

# test_x_clients.py
from company_api import CompanyApi

BASE_URL = "http://5.101.50.27:8000"

def test_get_one_company():
    api = CompanyApi(BASE_URL)

    name = "PyCharm"
    descr = "IDE"
    result = api.create_company(name, descr)
    new_id = result["id"]

    new_company = api.get_company(new_id)

    assert new_company["name"] == name
    assert new_company["description"] == descr
    assert new_company["is_active"] is True