describe,itveexpectile birim testleri yazmak ve doğru eşleştiriciyi (toBe,toEqual,toThrow) seçmek- Asenkron kodu ve hataları test etmek, kodu mock, spy ve sahte zamanlayıcılarla dış dünyadan yalıtmak
- İyi uygulamalara uymak: Arrange–Act–Assert, test piramidi, kapsam ve TDD
İndirim fonksiyonundaki bir hatayı düzeltiyorsun ve ödeme sayfası başka bir yerde bozuluyor. Her değişiklikten sonra siteyi elle denemek saatler sürer. Otomatik testler ise her dosya kaydedişinde yüzlerce durumu bir saniyede kontrol eder. JavaScript için en popüler test çalıştırıcıları Vitest ve Jest'tir; API'leri neredeyse aynıdır, bu yüzden bu derste öğrendiklerin ikisinde de çalışır.
İlk birim testi
Bir birim testi (unit test), küçük bir parçayı (genellikle tek bir fonksiyonu) yalıtılmış olarak kontrol eder. Test dosyası kodun yanında durur: price.js için price.test.js. describe testleri gruplar, it (ya da test) tek bir davranışı tarif eder, expect(değer).toBe(beklenen) ise kontrolü yapar. npx vitest testleri çalıştırır ve dosyaları izlemeye devam eder.
// price.js
export function applyDiscount(price, percent) {
if (percent < 0 || percent > 100) throw new RangeError('Percent must be 0-100');
return Math.round(price * (100 - percent)) / 100;
}
// price.test.js
import { describe, it, expect } from 'vitest';
import { applyDiscount } from './price.js';
describe('applyDiscount', () => {
it('reduces the price by the given percent', () => {
expect(applyDiscount(200, 10)).toBe(180);
});
it('rounds to cents', () => {
expect(applyDiscount(9.99, 15)).toBe(8.49);
});
it('rejects a percent above 100', () => {
expect(() => applyDiscount(50, 120)).toThrow(RangeError);
});
}); ✓ price.test.js (3 tests) 3ms
Test Files 1 passed (1)
Tests 3 passed (3)toThrow için çağrıyı bir ok fonksiyonuna sarmalısın; yoksa hata expect çalışmadan önce fırlatılır.expect nasıl çalışır
Bir test çalıştırıcısı sihir değildir: it fonksiyonunu try...catch içinde çağırır, expect ise koşul sağlanmazsa bir hata fırlatır. Aşağıdaki mini sürüm bunu gösterir ve iki önemli farkı ortaya çıkarır: toBe Object.is ile (aynı referans), toEqual ise değerlere göre karşılaştırır; ondalık sayıları kesin eşitlikle kontrol etmek ise risklidir.
const results = [];
function it(name, fn) {
try {
fn();
results.push(`✓ ${name}`);
} catch (error) {
results.push(`✗ ${name}: ${error.message}`);
}
}
function expect(actual) {
const show = (v) => JSON.stringify(v);
return {
toBe(expected) {
if (!Object.is(actual, expected)) throw new Error(`expected ${show(expected)}, got ${show(actual)}`);
},
toEqual(expected) {
if (show(actual) !== show(expected)) throw new Error(`expected ${show(expected)}, got ${show(actual)}`);
},
};
}
it('adds numbers', () => expect(2 + 3).toBe(5));
it('compares arrays by value', () => expect([1, 2]).toEqual([1, 2]));
it('toBe needs the same array', () => expect([1, 2]).toBe([1, 2]));
it('decimals are tricky', () => expect(0.1 + 0.2).toBe(0.3));
console.log(results.join('\n'));▸ Beklenen çıktı
✓ adds numbers ✓ compares arrays by value ✗ toBe needs the same array: expected [1,2], got [1,2] ✗ decimals are tricky: expected 0.3, got 0.30000000000000004
toBeCloseTo(0.3) vardır. Bizim toEqual'ımız basitleştirilmiştir; Vitest ve Jest derin karşılaştırmayı daha dikkatli yapar.| Eşleştirici | Neyi kontrol eder |
|---|---|
toBe(x) | Object.is ile eşitlik: ilkel değerler, aynı referans |
toEqual(x) | nesnelerin ve dizilerin derin eşitliği |
toBeCloseTo(x) | ondalık sayıların yaklaşık eşitliği |
toContain(x), toHaveLength(n) | dizide bir eleman ya da string'de bir parça; uzunluk |
toThrow(Type) | fonksiyonun hata fırlatması |
resolves / rejects | bir Promise'in sonucu ya da hatası |
.not | herhangi bir kontrolü olumsuzlar: expect(x).not.toBe(0) |
Asenkron kod, mock ve spy'lar
Asenkron bir test yalnızca bir async fonksiyondur: sonucu await ile bekle ya da await expect(promise).rejects.toThrow() yaz. Bir birim testi gerçek e-posta göndermemeli, gerçek bir sunucuya gitmemeli ve gerçek zamanı beklememelidir. Bunun için mock'lar (sahte fonksiyonlar, vi.fn()), spy'lar (var olan bir metodun çağrılarını izler, vi.spyOn) ve sahte zamanlayıcılar (vi.useFakeTimers()) vardır. En basit yol, bağımlılığı parametre olarak vermektir: böylece testte gerçek fonksiyon yerine bir mock verirsin.
// notify.js
export async function notifyUser(user, sendEmail) {
return sendEmail(user.email, `Welcome, ${user.name}!`);
}
// notify.test.js
import { describe, it, expect, vi } from 'vitest';
import { notifyUser } from './notify.js';
describe('notifyUser', () => {
it('sends one welcome email', async () => {
const sendEmail = vi.fn().mockResolvedValue({ ok: true });
await notifyUser({ email: 'leyla@mail.az', name: 'Leyla' }, sendEmail);
expect(sendEmail).toHaveBeenCalledTimes(1);
expect(sendEmail).toHaveBeenCalledWith('leyla@mail.az', 'Welcome, Leyla!');
});
it('fails when the mail service is down', async () => {
const sendEmail = vi.fn().mockRejectedValue(new Error('SMTP down'));
await expect(notifyUser({ email: 'a@mail.az', name: 'Elvin' }, sendEmail)).rejects.toThrow('SMTP down');
});
}); ✓ notify.test.js (2 tests) 4ms
Test Files 1 passed (1)
Tests 2 passed (2)vi yerine jest ile yazılır: jest.fn(), jest.spyOn(), jest.useFakeTimers().İyi testlerin kuralları
- Arrange–Act–Assert: hazırla, çalıştır, kontrol et; her test bu üç bölümden oluşur.
- Bir test, bir davranış; adı da neyin beklendiğini söyler: “rejects a percent above 100”.
- İç ayrıntıları değil davranışı test et; böylece testler yeniden düzenlemeden sağ çıkar.
- Test piramidi: çok sayıda hızlı birim testi, daha az entegrasyon testi ve birkaç uçtan uca (end-to-end) test.
Testler kırmızı: isLeapYear'da bir hata var. Testlere dokunmadan fonksiyonu düzelt ki dört testin hepsi yeşile dönsün. Kural: 4'e bölünen yıl artık yıldır, ama 100'e bölünen yıllar yalnızca 400'e de bölünüyorsa artık yıldır.
function isLeapYear(year) {
return year % 4 === 0;
}
const results = [];
function it(name, fn) {
try {
fn();
results.push(`✓ ${name}`);
} catch (error) {
results.push(`✗ ${name}: ${error.message}`);
}
}
const expect = (actual) => ({
toBe(expected) {
if (actual !== expected) throw new Error(`expected ${expected}, got ${actual}`);
},
});
it('2024 is a leap year', () => expect(isLeapYear(2024)).toBe(true));
it('2023 is not a leap year', () => expect(isLeapYear(2023)).toBe(false));
it('1900 is not a leap year', () => expect(isLeapYear(1900)).toBe(false));
it('2000 is a leap year', () => expect(isLeapYear(2000)).toBe(true));
console.log(results.join('\n'));▸ Beklenen çıktı
✓ 2024 is a leap year ✓ 2023 is not a leap year ✓ 1900 is not a leap year ✓ 2000 is a leap year
Kendi spy'ını yaz: createSpy(), her çağrıldığında argümanlarını spy.calls dizisine kaydeden bir fonksiyon döndürsün. Kontrolün gösterdiği gibi notifyAll yalnızca abonelere e-posta göndermeli; çağrı sayısı ve ikinci çağrının argümanları yazdırılır.
function createSpy() {
const spy = (...args) => {
// record args in spy.calls
};
spy.calls = [];
return spy;
}
function notifyAll(users, send) {
for (const user of users) {
if (user.subscribed) send(user.email, 'New lesson is out!');
}
}
const send = createSpy();
notifyAll([
{ email: 'aysel@mail.az', subscribed: true },
{ email: 'murad@mail.az', subscribed: false },
{ email: 'leyla@mail.az', subscribed: true },
], send);
console.log('calls:', send.calls.length);
console.log(send.calls[1]);▸ Beklenen çıktı
calls: 2 [ 'leyla@mail.az', 'New lesson is out!' ]
Önemli noktalar
- Bir birim testi küçük bir davranışı kontrol eder:
describegruplar,ittarif eder,expect(...).matcher()kontrol eder. toBeObject.isile,toEqualdeğere göre karşılaştırır; ondalıklar içintoBeCloseTo, hatalar içintoThrowkullan.- Asenkron testlerde
awaitya daresolves/rejects; mock, spy ve sahte zamanlayıcılar kodu ağdan, zamandan ve rastlantıdan yalıtır. - Arrange–Act–Assert, test başına bir davranış, bağımsız testler; çok sayıda birim, daha az entegrasyon ve uçtan uca test.
- Kapsam yalnızca hangi satırların çalıştığını gösterir; TDD, kırmızı → yeşil → düzenle demektir.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.
expect([1, 2]).toBe([1, 2]) neyle sonuçlanır?