Məzmuna keç

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.


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:

Terminal window
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-sqlite3 sinxrondur.get().all() sətirləri birbaşa qaytarır, await yoxdur. Bu, verilənlər bazası yoxlamalarını async testin 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.


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:

fixtures/db.fixture.js
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.


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

tests/db/order.spec.js
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: '[email protected]', 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.


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:

tests/db/security.spec.js
const { test, expect } = require('../../fixtures/db.fixture');
test('password is stored hashed', async ({ db }) => {
const row = db
.prepare('SELECT password FROM users WHERE email = ?')
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.


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)PostgresMySQL
Node sürücüsübetter-sqlite3pgmysql2
Qoşulmafayl yolubağlantı sətri (host, port, istifadəçi, parol, db)eyni
Yer-tutucu?$1, $2, …?
Çağırışlarsinxronasync (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/testmarket
await 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 var
expect(rows[0].status).toBe('pending');
expect(parseFloat(rows[0].total)).toBeCloseTo(59.98); // pg NUMERIC-i sətir kimi qaytarır

Keç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.


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'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 [email protected] üçü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ə.


İ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 qeyd.