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.
Ushbu bo‘lim mundarijasi
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:
pip install flask
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
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:
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.
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
pytest -q test_oqish.py
... [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 #
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
pytest -q test_yaratish.py
... [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:
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
pytest -q test_xatolar.py
....... [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 #
Holat o'zgarishi va to'qnashuv #
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
pytest -q test_band.py
.... [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.
Ikki xil yozuv:
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_client() juda qulay, lekin u ba'zi qatlamlarni chetlab
o'tadi:
| Chetlab o'tiladi | Nima yashirin qolishi mumkin |
|---|---|
| Haqiqiy TCP va TLS | Sertifikat va port sozlamalari |
| Teskari proksi (nginx) | Yo'nalish, sarlavha, hajm cheklovi |
| WSGI server sozlamalari | Vaqt tugashi, ishchilar soni |
| Bir vaqtda kelgan so'rovlar | Poyga 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:
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
pytest -q test_sarlavha.py
.. [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.
- Flask ni o'rnatib, ilovani va uchta o'qish testini ishga tushiring.
tozala()ni fixture dan olib tashlang - qaysi test yiqiladi?- Yaratish testlarini yozing va
201kodini tekshiring. 201ni200ga o'zgartiring - nechta test yiqiladi?- Majburiy maydonlar testini parametrlab yozing.
405testini ishga tushirib, Flask javobini o'qing.- Band qilish uchun
409holatini tekshiring. - Javobning butun tanasini tekshiradigan test yozing.
- Ilovaga
DELETE /kitoblar/<id>qo'shing va unga uchta test yozing. - 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.
idketma-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
parametrizebilan bitta testga jamlang. - Holat kodi - API shartnomasining qismi:
201ni200ga almashtirish mijozlarni buzadi. - Har testda kodni ham, tanani ham tekshiring.
405 Method Not Allowedkabi 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.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.