Məzmuna keç

Modul 8: Data-driven testlər

parametrize (Modul 2) artıq bir testi çoxlu sətir üzərində işlədir. Data-driven test növbəti addımı atır: o sətirləri koddan çıxar fayllara (JSON, CSV) qoy ki, verilənməntiq ayrı inkişaf etsin. Yeni hallar yeni sətirlərə çevrilir — test gövdəsinə redaktə yoxdur — və eyni verilən dəsti bir neçə testi qidalandıra bilər.

TestMarket Lab-ı http://localhost:3000-də işlək və .venv-ini aktiv saxla.

🎬 Video tezliklə əlavə olunacaq

Modul 8: data-driven testlərə qısa baxış — slayd təlimatı Slaydlar — yeni pəncərədə açılır

İnline formanı artıq bilirsən — dekoratorda tuple-lar siyahısı:

tests/test_inline.py
import pytest
import requests
BASE_URL = "http://localhost:3000"
@pytest.mark.parametrize(
"email, password, expected",
[
("[email protected]", "customer123", 200),
("[email protected]", "wrong", 401),
("", "", 400),
],
)
def test_login(email, password, expected):
r = requests.post(f"{BASE_URL}/api/auth/login", json={"email": email, "password": password})
assert r.status_code == expected

Bu, bir neçə hal üçün mükəmməldir. Cədvəl böyüyəndə — və ya proqramçı olmayan biri hal əlavə edə bilməli olanda — onu fayla köçür.

Yadda saxla: inline parametrize cədvəli bir neçə hal üçün idealdır. Verilən böyüyəndə, testlər arasında bölüşüləndə və ya koda toxunmadan redaktə edilə bilməli olanda xarici fayla köçür.


Halları bir JSON faylına — tests/data/login_cases.json — qoy, onu modul səviyyəsində yüklə (parametrize dekoratoru import zamanı işləyir, ona görə verilən onda mövcud olmalıdır) və siyahı üzərində parametrizə et (JSON-un şərhi yoxdur, ona görə fayl sadəcə massivdir):

[
{ "email": "[email protected]", "password": "customer123", "expected": 200 },
{ "email": "[email protected]", "password": "wrong", "expected": 401 },
{ "email": "[email protected]", "password": "whatever", "expected": 401 },
{ "email": "[email protected]", "password": "", "expected": 400 }
]
tests/test_login_json.py
import json
from pathlib import Path
import pytest
import requests
BASE_URL = "http://localhost:3000"
CASES = json.loads((Path(__file__).parent / "data" / "login_cases.json").read_text())
@pytest.mark.parametrize("case", CASES)
def test_login_from_json(case):
r = requests.post(
f"{BASE_URL}/api/auth/login",
json={"email": case["email"], "password": case["password"]},
)
assert r.status_code == case["expected"]

Path(__file__).parent verilən yolunu test faylına lövbərləyir, ona görə test istənilən qovluqdan işləyir. Hər JSON obyekti bir haldır; sətir əlavə edəndə test gövdəsi heç vaxt dəyişmir. (pytest halları avtomatik case0, case1, … etiketləyir — aşağıdakı Oxunaqlı id-lər bölməsi onları mənalı edir.)

Yadda saxla: xarici veriləni modul səviyyəsində yüklə (parametrize import zamanı işləyir). Fayl yollarını Path(__file__).parent ilə lövbərlə ki, testlər iş qovluğuna bağlı olmasın.


CSV cədvəlin yarada biləcəyi cədvəl-tipli hallara uyğundur — tests/data/categories.csv. csv.DictReader sətir başına bir dict verir, amma hər dəyər sətirdir, ona görə tipləri özün çevir (fayl sadəcə başlıq sətri üstəgəl verilədir — şərh sətri yox):

category,present
electronics,true
accessories,true
furniture,true
nonexistent,false
tests/test_categories_csv.py
import csv
from pathlib import Path
import pytest
import requests
BASE_URL = "http://localhost:3000"
def load_rows(name):
with open(Path(__file__).parent / "data" / name, newline="") as f:
return list(csv.DictReader(f))
ROWS = load_rows("categories.csv")
@pytest.mark.parametrize("row", ROWS, ids=[r["category"] for r in ROWS])
def test_category_presence(row):
products = requests.get(f"{BASE_URL}/api/products", params={"category": row["category"]}).json()
present = row["present"] == "true" # CSV values are strings — convert!
assert (len(products) > 0) is present

O present == "true" sətri klassik CSV tələsidir: "false" boş olmayan sətirdir və deməli truthy-dir, ona görə xam dəyərə güvənmək əvəzinə açıq şəkildə müqayisə etməlisən.

Yadda saxla: csv.DictReader sətir başına hamısı-sətir dəyərləri olan bir dict verir — assert etməzdən əvvəl tipləri çevir ("true"bool, "3"int). Bunu unutmaq #1 CSV baqıdır.


Uzun parametrize icraları, pytest hər halı xam dict ilə etiketləyirsə, oxunmaz olur. Veriləni hesablanmış ids= ötür ki, pytest -v mənalı adlar göstərsin:

@pytest.mark.parametrize(
"case",
CASES,
ids=[f"{c['email'] or 'empty'}->{c['expected']}" for c in CASES],
)
def test_login_labelled(case):
...
test_login_labelled[[email protected]>200] PASSED
test_login_labelled[[email protected]>401] PASSED
test_login_labelled[[email protected]>401] PASSED
test_login_labelled[[email protected]>400] PASSED

Yadda saxla: ids=-i hər halın verilənindən qur ki, uğursuzluqlar özlərini adlandırsın ([[email protected]>400]), qapalı bir dict göstərmək əvəzinə — bir baxışda hansı halın uğursuz olduğunu görürsən.


Niyə veriləni məntiqdən ayırmalı

Bölmə: “Niyə veriləni məntiqdən ayırmalı”

İkisini bölmək üç yolla haqqını verir:

  • Miqyas — 5 və ya 500 hal, test gövdəsi eynidir; yalnız sətir əlavə edirsən.
  • Sahiblik — manual QA və ya PM Python oxumadan CSV/JSON-a hal əlavə edə bilər.
  • Təkrar işlətmə — bir verilən dəsti həm API testini, həm də UI testini idarə edə bilər.

Test nəyin yoxlandığının tək aydın ifadəsinə çevrilir; fayl hansı girişlərin olduğunu saxlayır.

Yadda saxla: data-driven testlər yoxladığın nəyi (test gövdəsi) hansı girişlərdən (fayl) ayırır. Test kodu böyümədən əhatənin böyüməsinə imkan verən budur.


Modul 4-dəki login validasiya matrisi, indi fayl-idarəli — login_cases.json-a bir sətir əlavə edərək kənar hal əlavə et, başqa heç nə yox:

tests/test_login_matrix_json.py
import json
from pathlib import Path
import pytest
import requests
BASE_URL = "http://localhost:3000"
CASES = json.loads((Path(__file__).parent / "data" / "login_cases.json").read_text())
@pytest.mark.parametrize("case", CASES, ids=[f"{c['email'] or 'empty'}->{c['expected']}" for c in CASES])
def test_login_validation(case):
r = requests.post(
f"{BASE_URL}/api/auth/login",
json={"email": case["email"], "password": case["password"]},
)
assert r.status_code == case["expected"]

Yadda saxla: fayl-idarəli validasiya matrisi Modul 4-ün neqativ testinin yetkin formasıdır — eyni məntiq, hallar hər kəsin genişləndirə biləcəyi və başqa testlərin təkrar işlədə biləcəyi bir faylda.


TestMarket Lab işlək olarkən python-sdet layihəndə işlə. Verilən fayllarını tests/data/ altına qoy. pytest -v ilə işlət.

Tapşırıq 1 — JSON-idarəli login (10 dəq)

Bölmə: “Tapşırıq 1 — JSON-idarəli login (10 dəq)”

Dörd halla tests/data/login_cases.json düzəlt (valid → 200, səhv parol → 401, naməlum email → 401, çatışmayan parol → 400). Onu modul səviyyəsində yüklə və onun üzərində login testini parametrizə et.

Tapşırıq 2 — CSV kateqoriyaları (10 dəq)

Bölmə: “Tapşırıq 2 — CSV kateqoriyaları (10 dəq)”

category,present sətirləri ilə tests/data/categories.csv düzəlt. GET /api/products?category=<category>-nin yalnız present true olanda məhsul qaytardığını yoxlayan bir testi parametrizə et — "true"/"false" sətrini çevirməyi unutmayaraq.

Tapşırıq 3 — Oxunaqlı id-lər (5 dəq)

Bölmə: “Tapşırıq 3 — Oxunaqlı id-lər (5 dəq)”

İki testdən birinə ids= əlavə et ki, pytest -v [email protected]>200 kimi adlar göstərsin. Uğursuz olan bir sətir daxil et və hesabatın məhz həmin halı adlandırdığını təsdiqlə.

Tapşırıq 4 — Koda toxunmadan hal əlavə et (5 dəq)

Bölmə: “Tapşırıq 4 — Koda toxunmadan hal əlavə et (5 dəq)”

JSON/CSV-nə yeni sətir əlavə et (məsələn, boşluqlu email → 401 və ya 400 — əvvəlcə tətbiqin əsl davranışını yoxla) və yenidən işlət. Yeni halın test gövdəsinə dəyişiklik olmadan işlədiyini təsdiqlə.

Bonus — Bir verilən dəsti, iki test

Bölmə: “Bonus — Bir verilən dəsti, iki test”

Həm bir API testini, həm də (Modul 6/7-ni etmisənsə) bir UI login testini eyni login_cases.json-a yönəlt, bir verilən dəstinin fərqli qatları idarə edə biləcəyini sübut edərək.


Modul 9-da nəticələri oxunaqlı edirik — hesabat və dəst quruluşu: pytest-html/Allure hesabatları, testləri marker-lərlə təşkil etmək və komandaya təhvil verə biləcəyin bir dəst formalaşdırmaq.