13-bo‘lim
Uchidan-uchiga (E2E) testlar
Haqiqiy server va haqiqiy brauzer bilan test yozish, serverni kutish, Playwright, sahifa obyekti naqshi va nima uchun E2E testlar oz bo'lishi kerak.
Ushbu bo‘lim mundarijasi
Oldingi bo'limda test mijozi bilan ishladik - u tez, lekin tarmoqni chetlab o'tadi. Bu bo'limda esa dasturni haqiqatdan ishga tushiramiz: port ochiladi, so'rov tarmoq orqali boradi, brauzer sahifani chizadi.
Bunday testlar sekin va nozik. Shuning uchun ular oz bo'ladi - lekin ular topadigan xatolarni boshqa hech narsa topa olmaydi.
Testlanadigan ilova #
Avvalgi API ga sahifa qo'shamiz. Flask 12-bo'limdagi kabi kerak bo'ladi:
pip install flask
import sys
from flask import Flask, redirect, request
KITOBLAR = []
SAHIFA = """<!doctype html>
<html lang="uz">
<head><meta charset="utf-8"><title>Kutubxona</title></head>
<body>
<h1>Kutubxona</h1>
<ul id="royxat">{qatorlar}</ul>
<form method="post" action="/qosh">
<input name="nom" placeholder="Kitob nomi">
<input name="muallif" placeholder="Muallif">
<button type="submit">Qo'shish</button>
</form>
<p id="soni">Jami: {soni} ta</p>
</body>
</html>"""
app = Flask(__name__)
@app.get("/")
def bosh():
qatorlar = "".join(f"<li>{k['nom']} - {k['muallif']}</li>" for k in KITOBLAR)
return SAHIFA.format(qatorlar=qatorlar, soni=len(KITOBLAR))
@app.post("/qosh")
def qosh():
nom = (request.form.get("nom") or "").strip()
muallif = (request.form.get("muallif") or "").strip()
if nom and muallif:
KITOBLAR.append({"nom": nom, "muallif": muallif})
return redirect("/")
if __name__ == "__main__":
app.run(port=int(sys.argv[1]))
Serverni ko'tarish va kutish #
E2E testning birinchi qiyinchiligi - server tayyor bo'lishini
kutish. Buni sleep bilan qilmaslik kerak:
import socket
import subprocess
import sys
import time
import pytest
def bosh_port():
"""Bo'sh portni operatsion tizimdan so'raymiz."""
with socket.socket() as s:
s.bind(("127.0.0.1", 0))
return s.getsockname()[1]
def portni_kut(port, chegara=15.0):
"""Server javob berguncha kutadi - qat'iy sleep emas."""
tugash = time.monotonic() + chegara
while time.monotonic() < tugash:
try:
with socket.create_connection(("127.0.0.1", port), timeout=0.2):
return
except OSError:
time.sleep(0.05)
raise RuntimeError(f"server {chegara} soniyada ko'tarilmadi")
@pytest.fixture(scope="session")
def manzil():
port = bosh_port()
jarayon = subprocess.Popen(
[sys.executable, "veb.py", str(port)],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
try:
portni_kut(port)
yield f"http://127.0.0.1:{port}"
finally:
jarayon.terminate()
jarayon.wait(timeout=5)
@pytest.fixture(scope="session")
def brauzer():
# import shu yerda: brauzer testlari ishga tushmasa, playwright kerak emas
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
b = p.chromium.launch()
yield b
b.close()
Uchta qaror bu yerda muhim:
| Qaror | Sababi |
|---|---|
| Portni tizimdan so'rash | Qat'iy port band bo'lsa, test yiqilardi |
| Ulanish urinib ko'rish | sleep(2) ba'zan kam, ba'zan ko'p bo'ladi |
finally ichida to'xtatish | Test yiqilsa ham jarayon qolib ketmasin |
scope="session" | Serverni har test uchun ko'tarish juda sekin |
Haqiqiy tarmoq orqali test #
Endi brauzersiz, lekin haqiqiy HTTP orqali:
import urllib.parse
import urllib.request
def test_bosh_sahifa_ochiladi(manzil):
javob = urllib.request.urlopen(manzil)
assert javob.status == 200
matn = javob.read().decode("utf-8")
assert "<h1>Kutubxona</h1>" in matn
def test_sahifa_utf8_qaytaradi(manzil):
javob = urllib.request.urlopen(manzil)
assert "charset=utf-8" in javob.headers["Content-Type"]
def test_forma_yuborilgandan_keyin_qaytadi(manzil):
malumot = urllib.parse.urlencode({
"nom": "O'tkan kunlar",
"muallif": "Abdulla Qodiriy",
}).encode()
javob = urllib.request.urlopen(manzil + "/qosh", data=malumot)
assert javob.status == 200
assert "O'tkan kunlar" in javob.read().decode("utf-8")
pytest -q test_tarmoq.py
... [100%]
3 passed in 0.20s
Bu testlar 12-bo'limdagilardan sekinroq ishladi, lekin ular haqiqiy narsalarni tekshiradi: port ochilganmi, sarlavhalar to'g'rimi, qayta yo'naltirish ishlayaptimi.
Ko'p loyihalarda "E2E" deganda aynan shu - brauzersiz, haqiqiy HTTP testlari tushuniladi. Ular brauzerli testlardan o'n baravar tez va ancha barqaror.
Brauzer faqat sahifadagi JavaScript mantiqni tekshirish kerak bo'lganda zarur. Agar sahifangiz serverda to'liq chiziladigan bo'lsa, brauzer testisiz ham ishonch hosil qilish mumkin.
Brauzer bilan test #
Endi haqiqiy brauzerni ishga tushiramiz. Playwright - shu ishning eng keng tarqalgan vositalaridan biri:
pip install playwright
Kutubxonaning o'zi kichik, lekin unga brauzer kerak. Uni alohida buyruq yuklaydi - bu taxminan 100 MB va bir necha daqiqa oladi:
playwright install chromium
import pytest
@pytest.fixture
def sahifa(brauzer, manzil):
s = brauzer.new_page()
s.goto(manzil)
yield s
s.close()
def test_sahifa_sarlavhasi(sahifa):
assert sahifa.title() == "Kutubxona"
assert sahifa.inner_text("h1") == "Kutubxona"
def test_forma_toldirilib_yuboriladi(sahifa):
sahifa.fill("input[name=nom]", "Mehrobdan chayon")
sahifa.fill("input[name=muallif]", "Abdulla Qodiriy")
sahifa.click("button[type=submit]")
sahifa.wait_for_selector("#royxat li")
assert "Mehrobdan chayon" in sahifa.inner_text("#royxat")
def test_bosh_maydon_bilan_qoshilmaydi(sahifa):
oldingi = sahifa.inner_text("#soni")
sahifa.fill("input[name=nom]", "Faqat nom")
sahifa.click("button[type=submit]")
assert sahifa.inner_text("#soni") == oldingi
pytest -q test_brauzer.py
... [100%]
3 passed in 1.13s
Uchta test haqiqiy brauzerda ishladi: sahifa yuklandi, maydonlar to'ldirildi, tugma bosildi.
Endi ikki darajani yonma-yon o'lchaymiz:
pytest -q --durations=6 test_tarmoq.py test_brauzer.py
...... [100%]
========================= slowest 6 durations ==========================
0.55s setup test_brauzer.py::test_sahifa_sarlavhasi
0.15s setup test_tarmoq.py::test_bosh_sahifa_ochiladi
0.14s call test_brauzer.py::test_forma_toldirilib_yuboriladi
0.12s call test_brauzer.py::test_bosh_maydon_bilan_qoshilmaydi
0.05s teardown test_brauzer.py::test_bosh_maydon_bilan_qoshilmaydi
0.05s setup test_brauzer.py::test_bosh_maydon_bilan_qoshilmaydi
6 passed in 1.22s
Eng qimmat qator - brauzerning birinchi ishga tushishi
(setup). Undan keyin har sahifa ochilishi va har klik ham tarmoq
testlaridan bir necha baravar ko'proq vaqt oladi. Butun ro'yxat esa
12-bo'limdagi test mijozi testlaridan o'nlab baravar sekin: u yerda
yettita test 0.03 soniyada tugagan edi.
Sahifa obyekti naqshi #
Yuqoridagi testlarda input[name=nom] kabi selektorlar to'g'ridan-to'g'ri
yozilgan. Sahifa o'zgarsa, ularni hamma testda tuzatish kerak bo'ladi.
Sahifa obyekti (Page Object) - selektorlarni bir joyga yig'ish naqshi:
class KutubxonaSahifasi:
"""Sahifa bilan muloqotning yagona joyi."""
NOM = "input[name=nom]"
MUALLIF = "input[name=muallif]"
YUBORISH = "button[type=submit]"
ROYXAT = "#royxat"
SONI = "#soni"
def __init__(self, sahifa, manzil):
self.sahifa = sahifa
self.manzil = manzil
def och(self):
self.sahifa.goto(self.manzil)
return self
def kitob_qosh(self, nom, muallif):
self.sahifa.fill(self.NOM, nom)
self.sahifa.fill(self.MUALLIF, muallif)
self.sahifa.click(self.YUBORISH)
return self
def royxat_matni(self):
return self.sahifa.inner_text(self.ROYXAT)
def soni_matni(self):
return self.sahifa.inner_text(self.SONI)
import pytest
from sahifalar import KutubxonaSahifasi
@pytest.fixture
def kutubxona(brauzer, manzil):
s = brauzer.new_page()
yield KutubxonaSahifasi(s, manzil).och()
s.close()
def test_ikkita_kitob_qoshiladi(kutubxona):
kutubxona.kitob_qosh("O'tkan kunlar", "Abdulla Qodiriy")
kutubxona.kitob_qosh("Kecha va kunduz", "Cho'lpon")
matn = kutubxona.royxat_matni()
assert "O'tkan kunlar" in matn
assert "Kecha va kunduz" in matn
def test_soni_yangilanadi(kutubxona):
oldingi = kutubxona.soni_matni()
kutubxona.kitob_qosh("Sarob", "Abdulla Qahhor")
assert kutubxona.soni_matni() != oldingi
pytest -q test_sahifa_obyekti.py
.. [100%]
2 passed in 1.09s
Endi selektor o'zgarsa, faqat bitta joy tuzatiladi. Testlar esa o'qiladigan bo'lib qoldi: ular nima qilinayotganini aytadi, qanday bosilayotganini emas.
sleep yozmangBu eng ko'p uchraydigan xato va beqaror testlarning bosh sababi:
sahifa.click("button")
time.sleep(2) # umid qilamiz, yetadi
assert "Kitob" in sahifa.inner_text("#royxat")
Ikki soniya sizning kompyuteringizda yetadi, CI serverida esa yetmaydi - test tasodifan yiqila boshlaydi. Yoki teskarisi: hamma narsa 50 ms da tayyor bo'ladi, lekin har test 2 soniyani behuda sarflaydi.
To'g'risi - shartni kutish:
sahifa.click("button")
sahifa.wait_for_selector("#royxat li") # element paydo bo'lguncha
Playwright ko'p amallarni o'zi kutadi, lekin natija paydo bo'lishini aniq ko'rsatish kerak bo'lgan joylar qoladi.
E2E testlar qancha bo'lishi kerak #
| Yozing | Yozmang |
|---|---|
| Asosiy yo'l: ro'yxatdan o'tish, xarid, to'lov | Har bir forma maydoni uchun |
| Tizimlar orasidagi ulanish ishlayotganini | Kiritishni tekshirish qoidalarini |
| Muhim sahifa ochilishini | Hisob-kitob natijalarini |
| Chiqarishdan keyingi tutun testini | Xato xabarlari matnini |
Oxirgi qator alohida: tutun testi (smoke test) - serverga chiqarilgandan keyin "ilova umuman tirikmi" degan savolga javob beradigan bir necha test. Ular odatda eng foydali E2E testlar bo'ladi.
veb.pyni yozib, uni qo'lda ishga tushiring va brauzerda oching.conftest.pydagi fixture ni yozib, uchta tarmoq testini ishga tushiring.portni_kutnitime.sleep(0.1)ga almashtiring - test qanday o'zgaradi?- Serverni to'xtatishni
finallydan chiqaring va testni yiqiting - jarayon qoldimi? - Playwright va brauzerni o'rnatib, uchta brauzer testini ishga tushiring.
- Brauzer testlari tarmoq testlaridan necha baravar sekin?
wait_for_selectorni olib tashlang - test hali ham o'tadimi?- Sahifa obyektini yozib, testlarni unga o'tkazing.
input[name=nom]selektorini o'zgartiring va faqat bitta joyda tuzatganingizni ko'ring.- O'z loyihangiz uchun uchta E2E test tanlang - nima uchun aynan ularni?
Xulosa #
- E2E testi dasturni haqiqatdan ishga tushiradi: port ochiladi, so'rov tarmoq orqali boradi.
- Ikki daraja bor: brauzersiz haqiqiy HTTP va haqiqiy brauzer.
- Ko'p loyihalarda birinchisi yetarli; brauzer faqat sahifadagi JavaScript uchun zarur.
- Portni tizimdan so'rang (
bind(("127.0.0.1", 0))) - qat'iy port band bo'lishi mumkin. - Server tayyorligini
sleepbilan emas, ulanib ko'rish bilan kuting. - Jarayonni
finallyichida to'xtating - aks holda yiqilgan test ortidan server qolib ketadi. - Serverni
scope="session"bilan bir marta ko'taring. - Playwright haqiqiy brauzerni boshqaradi; brauzerning o'zi alohida yuklab olinadi (~100 MB).
- Brauzer testlari tarmoq testlaridan sezilarli sekin - shuning uchun ular kam bo'lsin.
- Sahifa obyekti naqshi selektorlarni bir joyga yig'adi; sahifa o'zgarsa, bitta fayl tuzatiladi.
- Sahifa obyekti bilan yozilgan test nima qilinayotganini aytadi, qanday bosilayotganini emas.
- E2E testda
time.sleepyozmang - u CI da yetmaydi, mahalliy mashinada esa isrof bo'ladi. - Shartni kuting:
wait_for_selectoryoki shunga o'xshash kutish usuli. - E2E testlar asosiy yo'llarni va chiqarishdan keyingi tutun testini qamrasin, mayda qoidalarni emas.
Keyingi bo'limda qoplamani o'lchaymiz va uning nimani ko'rsatmasligini ko'ramiz.
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.