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.

🕑 13 daqiqa o‘qish 📄 797 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Testlanadigan ilova
  2. Serverni ko'tarish va kutish
  3. Haqiqiy tarmoq orqali test
  4. Brauzer bilan test
  5. Sahifa obyekti naqshi
  6. E2E testlar qancha bo'lishi kerak
  7. Xulosa

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:

Terminal
pip install flask
Python
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:

Python
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:

QarorSababi
Portni tizimdan so'rashQat'iy port band bo'lsa, test yiqilardi
Ulanish urinib ko'rishsleep(2) ba'zan kam, ba'zan ko'p bo'ladi
finally ichida to'xtatishTest 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:

Python
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")
Terminal
pytest -q test_tarmoq.py
Natija
...                                                              [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.

Bu daraja ko'pincha yetarli

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:

Terminal
pip install playwright

Kutubxonaning o'zi kichik, lekin unga brauzer kerak. Uni alohida buyruq yuklaydi - bu taxminan 100 MB va bir necha daqiqa oladi:

Terminal
playwright install chromium
Python
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
Terminal
pytest -q test_brauzer.py
Natija
...                                                              [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:

Terminal
pytest -q --durations=6 test_tarmoq.py test_brauzer.py
Natija
......                                                           [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.

Bir xil xatti-harakatni uch darajada tekshirish Test mijozi Tarmoq yo'q Millisoniyalar Deyarli hech qachon beqaror bo'lmaydi Haqiqiy HTTP Port, sarlavha, yo'naltirish Soniyaning o'ndan biri Ba'zan port bandligi muammo qiladi Brauzer JavaScript, ko'rinish, klik Soniyalar Kutish, animatsiya va tarmoq beqarorlik beradi Xatti-harakatni eng pastda tekshiring, eng yuqorida esa faqat "ulanish ishlayaptimi"ni Brauzer testi butun mantiqni emas, bir-ikkita asosiy yo'lni qamrasin
Yuqoriga chiqqan sari test qimmatlashadi

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:

Python
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)
Python
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
Terminal
pytest -q test_sahifa_obyekti.py
Natija
..                                                               [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.

E2E testlarda sleep yozmang

Bu eng ko'p uchraydigan xato va beqaror testlarning bosh sababi:

Python
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:

Python
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 #

YozingYozmang
Asosiy yo'l: ro'yxatdan o'tish, xarid, to'lovHar bir forma maydoni uchun
Tizimlar orasidagi ulanish ishlayotganiniKiritishni tekshirish qoidalarini
Muhim sahifa ochilishiniHisob-kitob natijalarini
Chiqarishdan keyingi tutun testiniXato 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.

Amaliy topshiriq
  1. veb.py ni yozib, uni qo'lda ishga tushiring va brauzerda oching.
  2. conftest.py dagi fixture ni yozib, uchta tarmoq testini ishga tushiring.
  3. portni_kut ni time.sleep(0.1) ga almashtiring - test qanday o'zgaradi?
  4. Serverni to'xtatishni finally dan chiqaring va testni yiqiting - jarayon qoldimi?
  5. Playwright va brauzerni o'rnatib, uchta brauzer testini ishga tushiring.
  6. Brauzer testlari tarmoq testlaridan necha baravar sekin?
  7. wait_for_selector ni olib tashlang - test hali ham o'tadimi?
  8. Sahifa obyektini yozib, testlarni unga o'tkazing.
  9. input[name=nom] selektorini o'zgartiring va faqat bitta joyda tuzatganingizni ko'ring.
  10. 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 sleep bilan emas, ulanib ko'rish bilan kuting.
  • Jarayonni finally ichida 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.sleep yozmang - u CI da yetmaydi, mahalliy mashinada esa isrof bo'ladi.
  • Shartni kuting: wait_for_selector yoki 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.

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.