12-bo‘lim

API va HTTP testlari

Veb ilovani test mijozi orqali tekshirish, holat kodlari, JSON tanasi, kiritishni tekshirish, xato javoblari va API shartnomasini himoyalash.

🕑 13 daqiqa o‘qish 📄 643 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Ilova
  2. Test mijozi
  3. Yaratish va tekshirish
  4. Xato yo'llari - ishning yarmi
  5. Holat kodlari - shartnomaning bir qismi
  6. Holat o'zgarishi va to'qnashuv
  7. Sarlavhalar va autentifikatsiya
  8. Xulosa

Veb ilova - bu HTTP so'rovlarni javoblarga aylantiradigan funksiya. Demak uni ham xuddi funksiya kabi testlash mumkin: so'rov beramiz, javobni tekshiramiz.

Eng yaxshi tomoni - buning uchun serverni ko'tarish shart emas.

Ilova #

Kutubxona API sini Flask da yozamiz:

Terminal
pip install flask
Natija
Collecting flask
  Downloading flask-3.1.3-py3-none-any.whl.metadata (3.2 kB)
Collecting blinker>=1.9.0 (from flask)
  Downloading blinker-1.9.0-py3-none-any.whl.metadata (1.6 kB)
Collecting click>=8.1.3 (from flask)
  Downloading click-8.5.0-py3-none-any.whl.metadata (2.6 kB)
Collecting itsdangerous>=2.2.0 (from flask)
  Downloading itsdangerous-2.2.0-py3-none-any.whl.metadata (1.9 kB)
Collecting jinja2>=3.1.2 (from flask)
  Downloading jinja2-3.1.6-py3-none-any.whl.metadata (2.9 kB)
Collecting markupsafe>=2.1.1 (from flask)
  Downloading markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (2.7 kB)
Collecting werkzeug>=3.1.0 (from flask)
  Downloading werkzeug-3.1.8-py3-none-any.whl.metadata (4.0 kB)
Downloading flask-3.1.3-py3-none-any.whl (103 kB)
Downloading blinker-1.9.0-py3-none-any.whl (8.5 kB)
Downloading click-8.5.0-py3-none-any.whl (125 kB)
Downloading itsdangerous-2.2.0-py3-none-any.whl (16 kB)
Downloading jinja2-3.1.6-py3-none-any.whl (134 kB)
Downloading markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
Downloading werkzeug-3.1.8-py3-none-any.whl (226 kB)
Installing collected packages: markupsafe, itsdangerous, click, blinker, werkzeug, jinja2, flask

Successfully installed blinker-1.9.0 click-8.5.0 flask-3.1.3 itsdangerous-2.2.0 jinja2-3.1.6 markupsafe-3.0.3 werkzeug-3.1.8
Python
from flask import Flask, jsonify, request

KITOBLAR = {}
KEYINGI_ID = 1


def ilova_yarat():
    app = Flask(__name__)

    @app.get("/kitoblar")
    def royxat():
        return jsonify(list(KITOBLAR.values()))

    @app.get("/kitoblar/<int:kitob_id>")
    def bitta(kitob_id):
        kitob = KITOBLAR.get(kitob_id)
        if kitob is None:
            return jsonify({"xato": "kitob topilmadi"}), 404
        return jsonify(kitob)

    @app.post("/kitoblar")
    def qosh():
        global KEYINGI_ID
        malumot = request.get_json(silent=True) or {}
        for maydon in ("nom", "muallif"):
            if not malumot.get(maydon):
                return jsonify({"xato": f"{maydon} majburiy"}), 400
        kitob = {
            "id": KEYINGI_ID,
            "nom": malumot["nom"],
            "muallif": malumot["muallif"],
            "band": False,
        }
        KITOBLAR[KEYINGI_ID] = kitob
        KEYINGI_ID += 1
        return jsonify(kitob), 201

    @app.post("/kitoblar/<int:kitob_id>/band")
    def band(kitob_id):
        kitob = KITOBLAR.get(kitob_id)
        if kitob is None:
            return jsonify({"xato": "kitob topilmadi"}), 404
        if kitob["band"]:
            return jsonify({"xato": "kitob allaqachon band"}), 409
        kitob["band"] = True
        return jsonify(kitob)

    return app


def tozala():
    global KEYINGI_ID
    KITOBLAR.clear()
    KEYINGI_ID = 1

Test mijozi #

Flask ning test_client() metodi HTTP so'rovlarni tarmoqsiz bajaradi - u ilovaning o'zini to'g'ridan-to'g'ri chaqiradi:

Python
import pytest

import ilova


@pytest.fixture
def mijoz():
    ilova.tozala()
    app = ilova.ilova_yarat()
    app.config["TESTING"] = True
    with app.test_client() as c:
        yield c

ilova.tozala() har test oldidan ma'lumotni tozalaydi - bu 5-bo'limdagi "umumiy holat" qoidasining aynan o'zi.

Python
def test_bosh_royxat(mijoz):
    javob = mijoz.get("/kitoblar")

    assert javob.status_code == 200
    assert javob.get_json() == []


def test_mavjud_bolmagan_kitob(mijoz):
    javob = mijoz.get("/kitoblar/42")

    assert javob.status_code == 404
    assert javob.get_json() == {"xato": "kitob topilmadi"}


def test_notogri_yol(mijoz):
    javob = mijoz.get("/mavjud-emas")

    assert javob.status_code == 404
Terminal
pytest -q test_oqish.py
Natija
...                                                              [100%]
3 passed in 0.02s

Uchta test, tarmoq yo'q, port yo'q, server yo'q. Shuning uchun ular juda tez ishlaydi.

Yaratish va tekshirish #

Python
def test_kitob_qoshiladi(mijoz):
    javob = mijoz.post("/kitoblar", json={
        "nom": "O'tkan kunlar",
        "muallif": "Abdulla Qodiriy",
    })

    assert javob.status_code == 201
    kitob = javob.get_json()
    assert kitob["id"] == 1
    assert kitob["nom"] == "O'tkan kunlar"
    assert kitob["band"] is False


def test_qoshilgan_kitob_royxatda_korinadi(mijoz):
    mijoz.post("/kitoblar", json={"nom": "Kecha va kunduz", "muallif": "Cho'lpon"})

    javob = mijoz.get("/kitoblar")

    assert javob.status_code == 200
    assert len(javob.get_json()) == 1


def test_id_ketma_ket_beriladi(mijoz):
    birinchi = mijoz.post("/kitoblar", json={"nom": "A", "muallif": "X"})
    ikkinchi = mijoz.post("/kitoblar", json={"nom": "B", "muallif": "Y"})

    assert birinchi.get_json()["id"] == 1
    assert ikkinchi.get_json()["id"] == 2
Terminal
pytest -q test_yaratish.py
Natija
...                                                              [100%]
3 passed in 0.02s

Uchinchi test muhim: u fixture izolyatsiyasini ham tekshiradi. Agar tozala() ishlamaganda, id 1 dan emas, oldingi testlar qoldirgan sondan boshlanardi.

Xato yo'llari - ishning yarmi #

API testlarida eng ko'p unutiladigan narsa - noto'g'ri so'rovlar. Ularni parametrlash bilan bitta testga jamlaymiz:

Python
import pytest


@pytest.mark.parametrize("tana, kutilgan_xato", [
    ({}, "nom majburiy"),
    ({"nom": "Kitob"}, "muallif majburiy"),
    ({"muallif": "Muallif"}, "nom majburiy"),
    ({"nom": "", "muallif": "Muallif"}, "nom majburiy"),
    ({"nom": "Kitob", "muallif": ""}, "muallif majburiy"),
])
def test_majburiy_maydonlar(mijoz, tana, kutilgan_xato):
    javob = mijoz.post("/kitoblar", json=tana)

    assert javob.status_code == 400
    assert javob.get_json() == {"xato": kutilgan_xato}


def test_json_bolmagan_tana(mijoz):
    javob = mijoz.post("/kitoblar", data="bu json emas",
                       content_type="application/json")

    assert javob.status_code == 400


def test_notogri_metod(mijoz):
    javob = mijoz.delete("/kitoblar")

    assert javob.status_code == 405
Terminal
pytest -q test_xatolar.py
Natija
.......                                                          [100%]
7 passed in 0.03s

Oxirgi test 405 Method Not Allowed ni tekshiradi - buni hech kim qo'lda sinab ko'rmaydi, lekin API mijozlari uni ko'radi.

Holat kodlari - shartnomaning bir qismi #

Har amal uchun qaysi kod kutiladi Muvaffaqiyat 200 o'qish muvaffaqiyatli 201 yangi narsa yaratildi 204 bajarildi, tana yo'q Mijoz xatosi 400 ma'lumot noto'g'ri 401/403 ruxsat yo'q 404/409 yo'q / to'qnashuv Server xatosi 500 ichki xato 503 xizmat mavjud emas Testda hech qachon kutilmasligi kerak Holat kodi - API shartnomasining qismi 201 ni 200 ga almashtirsangiz, mijoz ilovalari buziladi - bu sezilmaydigan buzilish Shuning uchun har testda kodni ham, tanani ham tekshiring
Kod va tana - ikkalasi ham shartnoma

Holat o'zgarishi va to'qnashuv #

Python
import pytest


@pytest.fixture
def kitob_id(mijoz):
    javob = mijoz.post("/kitoblar", json={"nom": "O'tkan kunlar",
                                          "muallif": "Abdulla Qodiriy"})
    return javob.get_json()["id"]


def test_bosh_kitob_band_qilinadi(mijoz, kitob_id):
    javob = mijoz.post(f"/kitoblar/{kitob_id}/band")

    assert javob.status_code == 200
    assert javob.get_json()["band"] is True


def test_band_kitob_ikkinchi_marta_band_qilinmaydi(mijoz, kitob_id):
    mijoz.post(f"/kitoblar/{kitob_id}/band")

    javob = mijoz.post(f"/kitoblar/{kitob_id}/band")

    assert javob.status_code == 409
    assert javob.get_json() == {"xato": "kitob allaqachon band"}


def test_band_qilingan_kitob_oqishda_ham_band(mijoz, kitob_id):
    mijoz.post(f"/kitoblar/{kitob_id}/band")

    javob = mijoz.get(f"/kitoblar/{kitob_id}")

    assert javob.get_json()["band"] is True


def test_mavjud_bolmagan_kitobni_band_qilish(mijoz):
    javob = mijoz.post("/kitoblar/999/band")

    assert javob.status_code == 404
Terminal
pytest -q test_band.py
Natija
....                                                             [100%]
4 passed in 0.02s

Uchinchi test naqshiga e'tibor bering: o'zgarishni bitta so'rov bilan qilib, natijani boshqa so'rov bilan tekshirdik. Bu API testlarining eng ishonchli shakli - u ma'lumot haqiqatan saqlanganini isbotlaydi.

Javobning butun tanasini tekshiring, bitta maydonni emas

Ikki xil yozuv:

Python
assert javob.get_json()["nom"] == "O'tkan kunlar"          # yumshoq
assert javob.get_json() == {"id": 1, "nom": "O'tkan kunlar",
                            "muallif": "Abdulla Qodiriy", "band": False}

Ikkinchisi qattiqroq va ko'proq narsani tutadi: ortiqcha maydon qo'shilsa ham, maydon nomi o'zgarsa ham test yiqiladi.

Bu ayniqsa muhim, chunki API dan parolni yoki ichki maydonni tasodifan chiqarib yuborish shu yo'l bilan tutiladi. Faqat bitta maydonni tekshiradigan test buni sezmaydi.

Test mijozi - haqiqiy tarmoq emas

test_client() juda qulay, lekin u ba'zi qatlamlarni chetlab o'tadi:

Chetlab o'tiladiNima yashirin qolishi mumkin
Haqiqiy TCP va TLSSertifikat va port sozlamalari
Teskari proksi (nginx)Yo'nalish, sarlavha, hajm cheklovi
WSGI server sozlamalariVaqt tugashi, ishchilar soni
Bir vaqtda kelgan so'rovlarPoyga holati, qulflar

Shuning uchun test mijozi bilan yozilgan testlar - integratsiya darajasi, E2E emas. Haqiqiy tarmoq orqali o'tadigan bir-ikkita test ham kerak; ular keyingi bo'limda.

Sarlavhalar va autentifikatsiya #

So'rovga sarlavha qo'shish oddiy:

Python
def test_javob_json_turini_qaytaradi(mijoz):
    javob = mijoz.get("/kitoblar")

    assert javob.headers["Content-Type"] == "application/json"


def test_sorovga_sarlavha_qoshish(mijoz):
    javob = mijoz.get("/kitoblar", headers={"Authorization": "Bearer soxta-token"})

    assert javob.status_code == 200
Terminal
pytest -q test_sarlavha.py
Natija
..                                                               [100%]
2 passed in 0.01s

Haqiqiy loyihada ikkinchi test ruxsat tekshiruvini sinaydi. Unda uchta holat bo'lishi kerak: token yo'q (401), noto'g'ri token (401 yoki 403) va to'g'ri token (200). Uchtasi ham yozilishi shart - odatda birinchisi unutiladi.

Amaliy topshiriq
  1. Flask ni o'rnatib, ilovani va uchta o'qish testini ishga tushiring.
  2. tozala() ni fixture dan olib tashlang - qaysi test yiqiladi?
  3. Yaratish testlarini yozing va 201 kodini tekshiring.
  4. 201 ni 200 ga o'zgartiring - nechta test yiqiladi?
  5. Majburiy maydonlar testini parametrlab yozing.
  6. 405 testini ishga tushirib, Flask javobini o'qing.
  7. Band qilish uchun 409 holatini tekshiring.
  8. Javobning butun tanasini tekshiradigan test yozing.
  9. Ilovaga DELETE /kitoblar/<id> qo'shing va unga uchta test yozing.
  10. Loyihangizdagi bitta API yo'liga xato holatlari uchun testlar yozing.

Xulosa #

  • Veb ilova - so'rovni javobga aylantiradigan funksiya; uni funksiya kabi testlash mumkin.
  • Flask ning test_client() so'rovlarni tarmoqsiz bajaradi: port ham, server ham kerak emas.
  • Har test oldidan holat tozalanishi kerak - aks holda testlar bir-birining ma'lumotini ko'radi.
  • id ketma-ketligini tekshiradigan test izolyatsiyani ham tasdiqlaydi.
  • API testida xato yo'llari ishning yarmini tashkil qiladi va ko'pincha unutiladi.
  • Bir xil shakldagi noto'g'ri so'rovlarni parametrize bilan bitta testga jamlang.
  • Holat kodi - API shartnomasining qismi: 201 ni 200 ga almashtirish mijozlarni buzadi.
  • Har testda kodni ham, tanani ham tekshiring.
  • 405 Method Not Allowed kabi holatlarni hech kim qo'lda sinamaydi, lekin mijozlar ularni ko'radi.
  • O'zgarishni bir so'rov bilan qilib, natijani boshqa so'rov bilan tekshiring.
  • Javobning butun tanasini solishtiring - bu tasodifan chiqarib yuborilgan maydonni tutadi.
  • Test mijozi TCP, TLS, proksi va WSGI sozlamalarini chetlab o'tadi - shuning uchun bu E2E emas, integratsiya.
  • Ruxsat tekshiruvi uchun uchta test kerak: token yo'q, token noto'g'ri, token to'g'ri.

Keyingi bo'limda haqiqiy tarmoq va haqiqiy brauzerga chiqamiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.