Modul 14: Verilənlər bazasının yoxlanması
Capstone-u bitirdin — deməli bu modul qurduğun hər şeyin üstündə oturan senior addımdır. İndiyə qədər hər test tətbiqin bildirdiyinə güvənir: bir 201, bir cavab gövdəsi, yaşıl səhifə. Bu isə tətbiqin nəyi saxladığını yoxlayır — sifariş verdikdən sonra, verilənlər bazasında həqiqətən düzgün cəmi və düzgün sətir maddələri olan sətir varmı? “API uğur dedi — amma düzgün saxladımı?” Buna cavab vermək yetkinlik sıçrayışıdır və Node-da demək olar heç nəyə başa gəlmir: lazım olan verilənlər bazası sürücüsü, better-sqlite3, TestMarket Lab-ın özünün işlədiyi eyni sürücüdür.
TestMarket Lab-ı http://localhost:3000-də işlək saxla. Bu modul birbaşa Modul 9-dakı API çağırışları və Modul 8-dəki fixtures / API səpmə üzərində qurulur. SQL sənə yenidirsə, əvvəlcə SQL əsasları istinadını oxu — bu modul eyni ideyanın test tərəfidir.
🎬 Video tezliklə əlavə olunacaq
201-in sübut etmədiyi yan təsir
Bölmə: “201-in sübut etmədiyi yan təsir”201 və bir sifariş gövdəsi qaytaran POST /api/orders sənə tətbiqin sorğunu qəbul etdiyini deyir. Özlüyündə o, sifarişin düzgün yazıldığını demir — düzgün cəm hesablandı, düzgün sətir maddələri saxlandı, heç nə itmədi. Cavab tətbiqin sözüdür; verilənlər bazası sətri isə qeyddir.
Çox vaxt yoxlamaq üçün düzgün yer ictimai API-dir — GET /api/orders sənə lazım olan həqiqəti qaytarırsa, onu işlət. Verilənlər bazasına isə API-nin demədiyi hallar üçün enirsən: heç bir endpoint-in qaytarmadığı daxili sütun, ya da uyğun GET-i olmayan davam edən təsir. Verilənlər bazasını oxumaq testini sxemə bağlayır, ona görə ona qəsdən müraciət et — və onu yalnız-oxu saxla.
Yadda saxla: verilənlər bazasına qarşı yoxlamaq sadəcə cavabı yox, davam edən yan təsiri sübut edir. Eyni həqiqəti göstərdikdə ictimai API-yə üstünlük ver; API-nin gizlətdiyi üçün DB-yə en. Hər iki halda — yoxlamaq üçün oxu, yazma.
better-sqlite3 ilə qoşulmaq
Bölmə: “better-sqlite3 ilə qoşulmaq”TestMarket Lab hər şeyi bir SQLite faylında saxlayır — data/testmarket.db — və onu better-sqlite3 ilə oxuyur. Test dəstin eyni sürücünü işlədir, ona görə onu bir dəfə quraşdır:
npm install better-sqlite3İki toxunuş yoxlama bağlantısını təhlükəsiz və rahat edir:
- Onu yalnız-oxu aç
{ readonly: true }ilə. Yoxlama bağlantısının tətbiqin verilənlər bazasına yazmaqda heç bir işi yoxdur — yalnız-oxu bunu qeyri-mümkün edir, beləcə səhvən yazılmış bir sorğu tətbiqin sahib olduğu verilənləri heç vaxt korlaya bilməz.fileMustExist: trueəlavə et ki, səhv yol səssizcə boş verilənlər bazası yaratmaq əvəzinə səslə xəta versin. better-sqlite3sinxrondur —.get()və.all()sətirləri birbaşa qaytarır,awaityoxdur. Bu, verilənlər bazası yoxlamalarınıasynctestin içində bir neçə oxunaqlı sətir saxlayır.
const Database = require('better-sqlite3');const path = require('path');
// Bunu öz TestMarket Lab klonunun verilənlər bazası faylına yönəlt.const DB_PATH = path.resolve(__dirname, '../testmarket-lab/data/testmarket.db');
const db = new Database(DB_PATH, { readonly: true, fileMustExist: true });
const row = db .prepare('SELECT id, name, stock FROM products WHERE slug = ?') .get('wireless-mouse');console.log(row.id, row.name, row.stock); // -> 1 Wireless Mouse 50 (təzə səpilmiş DB-də)
db.close();? yer-tutucusuna və .get()-ə ötürülən 'wireless-mouse' arqumentinə diqqət et — dəyər SQL sətrinə yapışdırılmır, parametr kimi daxil olur. Bu, tam olaraq SQL əsasları istinadının izah etdiyi kimi, yeganə güzəştsiz vərdişdir.
Yadda saxla: tətbiqin verilənlər bazasını yalnız-oxu aç ({ readonly: true }) ki, test yalnız oxuya bilsin, .prepare(...).get() / .all() sətirləri sinxron qaytarır (await yoxdur) və dəyərləri həmişə ? parametrləri kimi ötür.
db fixture-i
Bölmə: “db fixture-i”O bağlantını bir fixture-ə bük ki, hər test təzə yalnız-oxu handle alsın və avtomatik bağlansın — Modul 8-dəki eyni fixture şablonu:
const base = require('@playwright/test');const Database = require('better-sqlite3');const path = require('path');
// Bunu öz TestMarket Lab klonunun verilənlər bazası faylına yönəlt.const DB_PATH = path.resolve(__dirname, '../../testmarket-lab/data/testmarket.db');
exports.test = base.test.extend({ db: async ({}, use) => { const db = new Database(DB_PATH, { readonly: true, fileMustExist: true }); await use(db); db.close(); },});exports.expect = base.expect;Verilənlər bazasını yoxlamaq istəyən test sadəcə db-ni istəyir — API modullarından artıq bildiyi daxili request fixture-i ilə yanaşı.
Yadda saxla: db fixture-i yalnız-oxu bağlantı açır və use-dan sonra onu bağlayır — digər fixture-lərinlə eyni quraşdırma/söküş forması, beləcə verilənlər bazası yoxlamaları mövcud dəstinə mərasimsiz düşür.
Sifarişi başdan-başa yoxla
Bölmə: “Sifarişi başdan-başa yoxla”Budur nəticə və hər yerdə təkrar işlədəcəyin şablon: API vasitəsilə hazırla və icra et, sonra verilənlər bazasına qarşı yoxla. request fixture-i ilə sifariş ver, sonra onun davam etdiyini sübut et — orders sətri və JOIN vasitəsilə onun order_items övladları:
const { test, expect } = require('../../fixtures/db.fixture');
test('order persists to the database', async ({ request, db }) => { // hazırla: təmiz, proqnozlaşdırıla bilən baza üçün yenidən səp await request.post('/api/reset');
// icra et: API vasitəsilə sifariş ver const res = await request.post('/api/orders', { data: { email: 'customer@test.io', items: [{ product_id: 1, quantity: 2 }] }, }); expect(res.status()).toBe(201); const { id: orderId } = await res.json();
// yoxla: sifariş sətri düzgün status və cəmlə saxlanıldı const order = db .prepare('SELECT status, total FROM orders WHERE id = ?') .get(orderId); expect(order).toBeDefined(); // sətir ümumiyyətlə var — o saxlandı expect(order.status).toBe('pending'); expect(order.total).toBeCloseTo(59.98); // Wireless Mouse 29.99 x 2
// yoxla: sətir maddələri saxlanıldı (JOIN orders -> order_items) const items = db .prepare( `SELECT oi.product_id, oi.quantity FROM orders o JOIN order_items oi ON oi.order_id = o.id WHERE o.id = ?`, ) .all(orderId); expect(items).toEqual([{ product_id: 1, quantity: 2 }]);});Bu, request fixture-inin baseURL-ini işlədir (capstone-dakı kimi playwright.config.js-də qurulub), beləcə /api/orders http://localhost:3000/api/orders-ə həll olunur. POST /api/reset əvvəlcə yenidən səpir, ona görə test təcrid olunub; sifarişin id-si düz cavabdan gəlir, beləcə onu heç vaxt təxmin etmirsən. API cəmi hesablayır və saxlayır; sən onun sadəcə qaytarıldığını yox, davam etdiyini yoxlayırsan. Yalnız-oxu db bağlantısı tətbiqin indicə commit etdiyi sifarişi görür — WAL da daxil olmaqla — çünki o, tətbiqin yazdığı eyni faylı açır.
Yadda saxla: forma API vasitəsilə hazırla/icra et → DB vasitəsilə yoxla-dır. 201 qəbul edildi deyir; orders sətri saxlanıldı deyir, order_items JOIN-i isə düzgün saxlanıldı deyir. Yalnız valideyni yox, övladları da yoxlamaq yarımçıq yazılmış sifarişi tutan şeydir.
API-nin gizlətdiyini oxumaq
Bölmə: “API-nin gizlətdiyini oxumaq”Verilənlər bazası heç bir endpoint-in göstərməyəcəyi bir şeyi sənə göstərəndə haqqını verir. TestMarket Lab istifadəçinin parolunu heç vaxt qaytarmır (haqlı olaraq) — deməli tətbiqin kimlik məlumatlarını açıq mətnlə saxlamaq əvəzinə hash-lədiyini yoxlamağın yeganə yolu baxmaqdır:
const { test, expect } = require('../../fixtures/db.fixture');
test('password is stored hashed', async ({ db }) => { const row = db .prepare('SELECT password FROM users WHERE email = ?') .get('customer@test.io'); const stored = row.password;
expect(stored).not.toBe('customer123'); // heç vaxt açıq-mətn parol expect(stored.startsWith('$2')).toBe(true); // bcrypt hash expect(stored).toHaveLength(60); // bcrypt hash-ləri 60 simvoldur});Bu, API vasitəsilə edə bilməyəcəyin real təhlükəsizlik yoxlamasıdır, çünki API — düzgün olaraq — parolu heç vaxt geri vermir. Bu, verilənlər bazası yoxlamasının bütün arqumentidir bir testdə: bəzi həqiqətlər yalnız sxemdə yaşayır.
Yadda saxla: API-nin qəsdən gizlətdiyi üçün verilənlər bazasına en. “Parol açıq mətnlə yox, hash-lənmiş saxlanılırmı?” xaricdən cavablana bilməz və içəridən bir SELECT uzaqlıqdadır.
Onu çoxunda-oxu saxla
Bölmə: “Onu çoxunda-oxu saxla”Diqqət et ki, yuxarıdakı hər test API vasitəsilə hazırlayır və verilənlər bazasını yalnız oxuyur. Bu qəsdəndir və fixtures modulunun öyrətdiyi eyni intizamdır: yazılar tətbiq vasitəsilə gedir (beləcə onun qaydaları, hesablanmış sütunları və məhdudiyyətlərinin hamısı işləyir) və verilənlər bazası sənin yoxlama qatındır, quraşdırma üçün arxa qapı deyil. Yalnız-oxu bağlantı bunu icra edir — db vasitəsilə INSERT etməyə cəhd et və better-sqlite3 SqliteError: attempt to write a readonly database atır. Təmiz baza üçün POST /api/reset ilə birləşdirdikdə, tətbiqin verilənlərinə heç vaxt əllə toxunmadan təcrid alırsan.
Yadda saxla: API ilə hazırla, DB ilə yoxla və yoxlama bağlantısını yalnız-oxu saxla. Test verilənlərini birbaşa tətbiqin verilənlər bazasına yazmaq onun məntiqini atlayır və real davranışdan uzaqlaşır — qoy tətbiq öz yazılarına sahib olsun.
Əlavə — SQLite-dan istehsal səviyyəli verilənlər bazalarına
Bölmə: “Əlavə — SQLite-dan istehsal səviyyəli verilənlər bazalarına”TestMarket Lab SQLite işlədir, çünki o, sıfır quraşdırma ilə tək bir fayldır — öyrənmək üçün mükəmməl. Real işlər klient-server verilənlər bazaları işlədir: Postgres, MySQL, SQL Server. Ürəkaçan hissə nə qədər az şeyin dəyişdiyidir. Şablon eynidir — qoşul → parametrləşdirilmiş sorğu → yoxla → bağla — və yazdığın SQL (SELECT, WHERE, JOIN) standartdır. Yalnız sürücü və bağlantı quraşdırması dəyişir.
| SQLite (bu kurs) | Postgres | MySQL | |
|---|---|---|---|
| Node sürücüsü | better-sqlite3 | pg | mysql2 |
| Qoşulma | fayl yolu | bağlantı sətri (host, port, istifadəçi, parol, db) | eyni |
| Yer-tutucu | ? | $1, $2, … | ? |
| Çağırışlar | sinxron | async (await) | async (await) |
Budur yuxarıdakı sifariş-sətri yoxlaması, pg ilə Postgres-ə qarşı yenidən yazılıb (npm install pg). SQL və məntiq dəyişməz keçir — yalnız sürücü, nömrələnmiş yer-tutucu ($1, ? yox), await və bir qaytarma-tipi özəlliyi fərqlənir:
const { Client } = require('pg');
// Kimlik məlumatları mühitdən gəlir, heç vaxt sabit kodlanmır.const client = new Client({ connectionString: process.env.DATABASE_URL });// məsələn postgresql://testuser:testpass@localhost:5432/testmarketawait client.connect();
const { rows } = await client.query( 'SELECT status, total FROM orders WHERE id = $1', // $1, ? yox [orderId],);await client.end();
expect(rows).toHaveLength(1); // sifariş sətri varexpect(rows[0].status).toBe('pending');expect(parseFloat(rows[0].total)).toBeCloseTo(59.98); // pg NUMERIC-i sətir kimi qaytarırKeçidin gətirdiyi iki kiçik özəllik: yer-tutucular ? əvəzinə nömrələnmişdir ($1, $2) və pg bir NUMERIC sütunu sətir kimi qaytarır (səssiz float yuvarlaqlaşmasından qaçmaq üçün), ona görə müqayisə etməzdən əvvəl onu parseFloat(...)-a bük.
Postgres haradan gəlir? Bir konteynerdən — lokalda bir docker run və CI-də bir servis konteyneri. Hər ikisi Docker əsasları istinadında əhatə olunub: o, Docker-i necə quraşdırmağı, dəqiq docker run postgres əmrini, bir docker-compose.yml-i və hər test işə salışına öz təzə verilənlər bazasını verən GitHub Actions services: postgres: blokunu göstərir. DATABASE_URL-i o konteynerə yönəlt və yuxarıdakı yoxlama dəyişmədən işləyir.
Yadda saxla: istehsal sürücünü və bağlantı sətrini dəyişir, ideyanı yox. better-sqlite3 → pg, fayl yolu → bağlantı sətri, ? → $1 — qoşul/parametrləşdir/yoxla/bağla şablonu və SQL-in toxunulmadan keçir. Bir iş “SQL / DB testi” deyəndə, məhz budur və sən onu artıq bilirsən.
Tapşırıqlar
Bölmə: “Tapşırıqlar”TestMarket Lab işlək olarkən capstone test layihəndə işlə. DB_PATH-i öz klonunun data/testmarket.db-nə yönəlt.
Tapşırıq 1 — Yalnız-oxu db fixture-i (10 dəq)
Bölmə: “Tapşırıq 1 — Yalnız-oxu db fixture-i (10 dəq)”db fixture-ini fixtures/db.fixture.js-ə əlavə et. Wireless Mouse-u slug-ı ilə (wireless-mouse) oxuyan və stock === 50 yoxlayan bir test yaz. Sonra onun yalnız-oxu olduğunu sübut et: db vasitəsilə INSERT cəhd et və SqliteError: attempt to write a readonly database atdığını təsdiqlə.
Tapşırıq 2 — Sifarişin davam etdiyini yoxla (15 dəq)
Bölmə: “Tapşırıq 2 — Sifarişin davam etdiyini yoxla (15 dəq)”order persists to the database testini təkrar et: bir sifariş POST et (məhsul 1, miqdar 2), sonra orders sətrinin status === 'pending' və total-ın 59.98-ə yaxın olduğunu, order_items-ə JOIN-in isə tam olaraq [{ product_id: 1, quantity: 2 }] verdiyini yoxla.
Tapşırıq 3 — API-nin gizlətdiyinə çat (10 dəq)
Bölmə: “Tapşırıq 3 — API-nin gizlətdiyinə çat (10 dəq)”password is stored hashed testini yaz: users-i customer@test.io üçün sorğula və saxlanılan parolun bcrypt hash olduğunu (startsWith('$2'), uzunluq 60) və açıq-mətn customer123 olmadığını yoxla.
Tapşırıq 4 — Sətir maddələrini say (10 dəq)
Bölmə: “Tapşırıq 4 — Sətir maddələrini say (10 dəq)”İki fərqli məhsulla bir sifariş POST et, sonra order_items-ə qarşı tək bir SELECT COUNT(*) … WHERE order_id = ? işlədərək tam iki sətrin saxlanıldığını yoxla. Onu sətirləri gətirib JS-də saymaqla müqayisə et — hansı daha yaxşı oxunur?
Çətinlik — Postgres-ə apar
Bölmə: “Çətinlik — Postgres-ə apar”Docker əsasları istinadını izləyərək bir Postgres konteyneri qaldır, DATABASE_URL qur və əlavənin pg yoxlamasını ona qarşı işlət. SQLite testindən yeganə dəyişikliklərin bağlantı, ? → $1 yer-tutucusu və cəmi parseFloat-a bükmək olduğunu təsdiqlə.
Bu səni harada qoyur
Bölmə: “Bu səni harada qoyur”İndi UI və API-nin üstündə oturduğu qatı yoxlaya bilərsən: davam edən həqiqəti. Bu, əksər avtomatlaşdırma kurslarının atladığı və əksər SDET müsahibələrinin araşdırdığı bacarıqdır — “onun həqiqətən saxlandığını haradan bilirsən?” Sən yalnız-oxu bağlantı, parametrləşdirilmiş sorğu və JOIN ilə cavab verirsən — bu gün SQLite-da, işdə Postgres-də, eyni dörd addımla. Bunu capstone dəstinə bərkit və testin bütün yığınını əhatə etdin: cavab, UI və qeyd.