---
title: 'API Testi'
source: 'https://academia.sh/tr/kurslar/tumlesim-testi/api-testi'
course: 'Tümleşim, Sözleşme ve Uçtan Uca Test'
language: tr
updated: '2026-08-17T18:10:50+00:00'
license: 'CC BY-SA 4.0'
---

# API Testi

Canlı bir servisin yanıtının şemaya karşı denetlenmesi: donmuş kaydın göremediği alan değişikliğinin gerçek istekle yakalanması, yapısal uyuşmazlık ile anlamsal uyuşmazlığın ayrılması ve denetimin bedelinin denetlenen alan, giden istek ve süre payıyla ölçülmesi.

Önceki ders dış servisin yanıtını kaydedip tekrar oynatmayı kurdu ve bir uyarıyla bitti:
kayıt, servisin **dünkü** davranışını dondurur. Katalog servisi bugün bir alanın adını
değiştirdiğinde kayda karşı koşan test hâlâ yeşil kalır, çünkü karşılaştırdığı şey servisin
kendisi değil, dünkü kopyasıdır.

Bu ders o boşluğu kapatan denetimi kurar. Sorusu API tasarımı değildir; istek yöntemleri,
durum kodları ve sürümleme Web API Tasarımı kursunda kuruldu ve burada tekrarlanmaz.
Buradaki soru şudur: canlı bir servisin yanıtı tüketicinin beklediği biçimde mi geliyor,
bunu hangi denetim söylüyor, hangi kusur sınıfını görerek ve hangisini göremeyerek.

## Donmuş Kaydın Sessizliği

Kütüphane ödünç sisteminde iki servis var: kitap kayıtlarını tutan **katalog servisi** ve
üyeye ödünç veren **ödünç servisi**. Ödünç servisi katalogdan bir kitap kaydı okur ve dört
alanına bakar: durum, raftaki adet, ödünç süresi ve kimlik. Aşağıdaki betik dün kaydedilen
gövdeyi ve bugünkü gövdeyi aynı karar işlevine verir.

```js
// donmus.mjs — dun kaydedilen katalog yaniti ile bugunku yanit, ayni tuketici islevinde
const KAYIT = {
  isbn: "978-0262033848", baslik: "Algoritmalara Giris", yazar: "Cormen",
  basimYili: 2009, sube: "merkez", toplamAdet: 4, raftaAdet: 2,
  durum: "rafta", oduncGun: 14,
};

// Katalog servisi bugun alan adini degistirdi: oduncGun -> oduncSuresiGun.
const CANLI = { ...KAYIT, oduncSuresiGun: KAYIT.oduncGun };
delete CANLI.oduncGun;

// Odunc servisinin karar islevi: kayitta ne varsa onu okur.
const oduncKarari = (kitap, bugun) =>
  kitap.durum !== "rafta" || kitap.raftaAdet < 1
    ? { verilir: false, sonGun: null }
    : { verilir: true, sonGun: bugun + kitap.oduncGun };

const BUGUN = 20260, ortak = Object.keys(KAYIT).filter((a) => a in CANLI);
for (const [ad, govde] of [["kayit", KAYIT], ["canli", CANLI]]) {
  const karar = oduncKarari(govde, BUGUN);
  console.log(`${ad.padEnd(6)} alan ${String(Object.keys(govde).length).padStart(2)}` +
    `  verilir=${karar.verilir}  sonGun=${karar.sonGun}`);
}
const eksik = Object.keys(KAYIT).filter((a) => !(a in CANLI));
console.log(`ortusen alan ${ortak.length}/${Object.keys(KAYIT).length}; canli yanitta olmayan: ${eksik.join(", ")}`);
console.log(`tekrar oynatma testi kayda karsi kosar -> sonGun 20274 bekleniyor, yesil`);
```

```
kayit  alan  9  verilir=true  sonGun=20274
canli  alan  9  verilir=true  sonGun=NaN
ortusen alan 8/9; canli yanitta olmayan: oduncGun
tekrar oynatma testi kayda karsi kosar -> sonGun 20274 bekleniyor, yesil
```

İki gövde de dokuz alan taşıyor ve sekizi örtüşüyor. Ödünç servisi ise canlı yanıtla
çalıştığında son günü hesaplayamıyor. Kayıt ve tekrar oynatma düzeni bunu göremez, çünkü
canlı servise **hiç istek gitmez**; yakalanan hata sınıfı sıfırdır. Eksik olan denetim,
yanıtın gerçekten geldiği yerden alınıp yazılı bir beklentiye karşı sınanmasıdır.

## Yanıtın Şeması

**Şema** yanıtın hangi alanları hangi türde taşıdığını yazılı hâle getirir. Aşağıdaki tanım
dokuz alan ve on altı kural içeriyor; kurallar tür, desen, değer kümesi ve alt sınırdan
ibarettir. Denetleyici de birkaç satırdır ve bir uyuşmazlık listesi döndürür.

```js
// sema.mjs — katalog yanitinin semasi ve alan alan denetleyici
export const KITAP_SEMA = {
  isbn: { tip: "dizgi", desen: /^97[89]-\d{10}$/ },
  baslik: { tip: "dizgi" },
  yazar: { tip: "dizgi" },
  basimYili: { tip: "tamsayi", enAz: 1400 },
  sube: { tip: "dizgi", kume: ["merkez", "sahil", "tepe"] },
  toplamAdet: { tip: "tamsayi", enAz: 0 },
  raftaAdet: { tip: "tamsayi", enAz: 0 },
  durum: { tip: "dizgi", kume: ["rafta", "oduncte", "kayip"] },
  oduncGun: { tip: "tamsayi", enAz: 1 },
};

const tipiTut = (kural, d) => kural.tip === "tamsayi"
  ? Number.isInteger(d)
  : typeof d === "string";

// Donen dizi bostur ya da her satiri bir uyusmazliktir.
export function semayaGore(sema, govde) {
  const uyusmazlik = [];
  for (const [ad, kural] of Object.entries(sema)) {
    const d = govde[ad];
    if (d === undefined) { uyusmazlik.push(`${ad}: alan yok`); continue; }
    if (!tipiTut(kural, d)) { uyusmazlik.push(`${ad}: tip ${kural.tip} degil (${typeof d})`); continue; }
    if (kural.desen && !kural.desen.test(d)) uyusmazlik.push(`${ad}: desene uymuyor`);
    if (kural.kume && !kural.kume.includes(d)) uyusmazlik.push(`${ad}: kume disi deger ${d}`);
    if (kural.enAz !== undefined && d < kural.enAz) uyusmazlik.push(`${ad}: ${kural.enAz} altinda`);
  }
  const fazla = Object.keys(govde).filter((a) => !(a in sema));
  if (fazla.length) uyusmazlik.push(`semada olmayan alan: ${fazla.join(", ")}`);
  return uyusmazlik;
}
```

Şemanın söyledikleri ile söylemedikleri arasındaki sınır bu dersin ekseni. Şema `raftaAdet`
alanının sıfırdan küçük olamayacağını söyler; o sayının **doğru** sayı olduğunu söylemez.

## Canlı Servise Karşı Denetim

Katalog servisi yerel bir süreçtir ve bir çevre değişkeniyle bilerek bozulabilir. Üç kusur
tanımlı: `tip` raf adedini dizgi olarak döndürür, `anlam` raf adedi yerine toplam adedi
yazar, `deger` başvuru kitabının yedi günlük ödünç süresini on dörde çıkarır.

```js
// katalog.mjs — katalog servisi; KATALOG_KUSUR degiskeniyle bilerek bozulur
// Kullanim: node katalog.mjs <port|0>   port 0 verilirse bos bir port secilir.
import { createServer } from "node:http";

if (process.argv[2] === undefined) {
  console.log("kullanim: node katalog.mjs <port|0>");
  process.exit(0);
}

const KUSUR = process.env.KATALOG_KUSUR ?? "yok";
const KITAPLAR = {
  "978-0262033848": { isbn: "978-0262033848", baslik: "Algoritmalara Giris", yazar: "Cormen",
    basimYili: 2009, sube: "merkez", toplamAdet: 4, raftaAdet: 2, durum: "rafta", oduncGun: 14 },
  "978-0201896831": { isbn: "978-0201896831", baslik: "Basvuru El Kitabi", yazar: "Knuth",
    basimYili: 1997, sube: "tepe", toplamAdet: 3, raftaAdet: 0, durum: "oduncte", oduncGun: 7 },
};

const disaVer = (k) => {
  if (KUSUR === "tip") return { ...k, raftaAdet: String(k.raftaAdet) };
  if (KUSUR === "anlam") return { ...k, raftaAdet: k.toplamAdet };
  if (KUSUR === "deger") return { ...k, oduncGun: 14 };
  return { ...k };
};

const sunucu = createServer((istek, yanit) => {
  yanit.sendDate = false;
  const kitap = KITAPLAR[istek.url.split("?")[0].replace("/kitap/", "")];
  yanit.writeHead(kitap ? 200 : 404, { "content-type": "application/json; charset=utf-8" });
  yanit.end(JSON.stringify(kitap ? disaVer(kitap) : { hata: "kitap yok" }));
});

sunucu.listen(Number(process.argv[2]), "127.0.0.1", () =>
  console.log(`hazir ${sunucu.address().port} kusur=${KUSUR}`));
```

Test dosyası servisi ayrı bir süreç olarak başlatır, seçilen portu sürecin ilk satırından
okur ve üç şey sınar: şemaya uygunluk, bir **değişmez** (invariant) ve tüketicinin kararı.

```js
// api.test.mjs — canli katalog servisine karsi istek/yanit denetimi
// Kullanim: KATALOG_KUSUR=yok|tip|anlam|deger node --test api.test.mjs
import { test, before, after } from "node:test";
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { KITAP_SEMA, semayaGore } from "./sema.mjs";

let surec, taban;

before(() => new Promise((coz) => {
  surec = spawn("node", ["katalog.mjs", "0"], { env: process.env });
  surec.stdout.once("data", (v) => {
    taban = `http://127.0.0.1:${String(v).split(" ")[1]}`;
    coz();
  });
}));
after(() => surec.kill());

const kitap = async (isbn) => (await fetch(`${taban}/kitap/${isbn}`)).json();

test("sema: iki kitap yaniti da semaya uyar", async () => {
  for (const isbn of ["978-0262033848", "978-0201896831"]) {
    const uyusmazlik = semayaGore(KITAP_SEMA, await kitap(isbn));
    assert.equal(uyusmazlik.length, 0, `${isbn} -> ${uyusmazlik.join("; ")}`);
  }
});

test("degismez: oduncte olan kitabin rafta adedi sifirdir", async () => {
  const k = await kitap("978-0201896831");
  assert.equal(k.durum === "oduncte" && k.raftaAdet > 0, false,
    `${k.isbn} -> durum ${k.durum} ama raftaAdet ${k.raftaAdet}`);
});

test("tuketici: rafta olan kitap icin son gun hesaplanir", async () => {
  const k = await kitap("978-0262033848");
  assert.equal(k.durum, "rafta");
  assert.equal(Number.isInteger(20260 + k.oduncGun), true);
});
```

Aynı takım servisin dört durumuna karşı koşturulur: sağlam sürüm ve üç kusur.

```bash
for k in yok tip anlam deger; do
  echo "== kusur=$k =="
  KATALOG_KUSUR=$k node --test --test-reporter=tap api.test.mjs | grep -E '^(ok|not ok|# (pass|fail))'
done
```

```
== kusur=yok ==
ok 1 - sema: iki kitap yaniti da semaya uyar
ok 2 - degismez: oduncte olan kitabin rafta adedi sifirdir
ok 3 - tuketici: rafta olan kitap icin son gun hesaplanir
# pass 3
# fail 0
== kusur=tip ==
not ok 1 - sema: iki kitap yaniti da semaya uyar
ok 2 - degismez: oduncte olan kitabin rafta adedi sifirdir
ok 3 - tuketici: rafta olan kitap icin son gun hesaplanir
# pass 2
# fail 1
== kusur=anlam ==
ok 1 - sema: iki kitap yaniti da semaya uyar
not ok 2 - degismez: oduncte olan kitabin rafta adedi sifirdir
ok 3 - tuketici: rafta olan kitap icin son gun hesaplanir
# pass 2
# fail 1
== kusur=deger ==
ok 1 - sema: iki kitap yaniti da semaya uyar
ok 2 - degismez: oduncte olan kitabin rafta adedi sifirdir
ok 3 - tuketici: rafta olan kitap icin son gun hesaplanir
# pass 3
# fail 0
```

Kırmızıdan yeşile dönüş bu tablonun içindedir. Tür kusuru konduğunda birinci test düşüyor;
katalog servisi raf adedini yeniden tamsayı olarak döndürdüğünde, yani `kusur=yok`
satırında, aynı test yeşile dönüyor. Bu döngü denetimin çalıştığının kanıtıdır.

## Yakalanan ve Kaçan Sınıflar

Düşen testlerin gerekçesi tek satırda okunabilir.

```bash
for k in tip anlam deger; do
  printf 'kusur=%-6s ' "$k"
  KATALOG_KUSUR=$k node --test --test-reporter=tap api.test.mjs |
    grep -A1 'error: |-' | tail -1 | sed 's/^ *//' | grep . || echo "dusen test yok"
done
```

```
kusur=tip    978-0262033848 -> raftaAdet: tip tamsayi degil (string)
kusur=anlam  978-0201896831 -> durum oduncte ama raftaAdet 3
kusur=deger  dusen test yok
```

**Şema denetiminin yakaladığı sınıf yapısal uyuşmazlıktır:** alanın yokluğu, türünün
değişmesi, desenin ya da değer kümesinin dışına çıkılması. Tür kusuru bu sınıfa girer ve
uyuşmazlık alan adıyla bildirilir. Dersin açılışındaki alan adı değişikliği de aynı sınıftandır;
canlı servise karşı koşan bir şema denetimi onu ilk istekte görür.

**Şema denetiminin kaçırdığı sınıf anlamsal uyuşmazlıktır.** Anlam kusurunda raf adedi hâlâ
sıfırdan büyük bir tamsayı; şemanın bütün kuralları sağlanıyor ve birinci test yeşil kalıyor.
Bu kusuru gören şey şema değil, elle yazılmış bir değişmezdir: ödünçte olan bir kitabın raf
adedi sıfır olmalıdır. Değişmez, iki alan **arasındaki** ilişkiyi söyler; şema ise her alanı
tek tek ele alır. Kaçan sınıfı kapatmanın bedeli budur — her ilişki tek tek yazılır.

Üçüncü kusur ikisinin de dışında kalıyor. Başvuru kitabının ödünç süresi yedi gün olmalıyken
on dört dönüyor; on dört, şemanın kabul ettiği bir tamsayı ve hiçbir değişmezi çiğnemiyor.
Üç testin üçü de yeşil. Burada eksik olan denetim değil **beklenen sonucun kaynağıdır**: bu
kitabın süresinin yedi olduğunu bilen tek yer sözleşmenin kendisidir. Kalite ve Test
Temelleri kursundaki test kâhini sorunu bu noktada yeniden ortaya çıkar ve sonraki iki ders
bu kaynağı iki ayrı yerden getirir.

## Denetimin Bedeli

Bedel iki türlü ölçülür: koşumdan bağımsız sayılar ve koşum süresinin nereye gittiği.

```js
// bedel.mjs — api testinin bedeli: kac alan denetlendi, kac istek gitti, sure nereye gitti
import { spawn } from "node:child_process";
import { KITAP_SEMA, semayaGore } from "./sema.mjs";

const ISBN = ["978-0262033848", "978-0201896831"];
const surec = spawn("node", ["katalog.mjs", "0"], { env: process.env });
const port = await new Promise((coz) => surec.stdout.once("data", (v) => coz(String(v).split(" ")[1])));
const taban = `http://127.0.0.1:${port}`;
const TEKRAR = 50, govdeler = [];

const t0 = performance.now();
for (let i = 0; i < TEKRAR; i += 1) {
  for (const isbn of ISBN) govdeler.push(await (await fetch(`${taban}/kitap/${isbn}`)).json());
}
const t1 = performance.now();
for (const g of govdeler) semayaGore(KITAP_SEMA, g);
const t2 = performance.now();
surec.kill();

const alan = Object.keys(KITAP_SEMA).length;
const kural = Object.values(KITAP_SEMA).reduce((n, k) => n + Object.keys(k).length, 0);
console.log(`sema: ${alan} alan, ${kural} kural`);
console.log(`kosum: ${govdeler.length} istek, ${govdeler.length * alan} alan denetimi, 2 surec`);
console.log(`istek suresi denetim suresinden buyuk : ${t1 - t0 > t2 - t1}`);
console.log(`oran en az elli kat                   : ${(t1 - t0) / (t2 - t1) >= 50}`);
```

```
sema: 9 alan, 16 kural
kosum: 100 istek, 900 alan denetimi, 2 surec
istek suresi denetim suresinden buyuk : true
oran en az elli kat                   : true
```

Mutlak süreler makineye bağlıdır; bu yüzden çıktı ham milisaniye yerine iki karşılaştırma
bildiriyor. Değişmeyen şey oranın yönüdür: sürenin neredeyse tamamı istek gidip gelmesinde
geçiyor, şema denetiminin kendisi ihmal edilebilir. API testinin bedeli denetimin
karmaşıklığı değil, **süreç dışına çıkma** kararıdır — bu takım iki süreç ayağa kaldırıyor
ve her sınama için ağ üzerinden istek yolluyor.

Koşumdan bağımsız sayılar bakımın ölçüsüdür. Dokuz alan ve on altı kural elle yazılmıştır
ve katalog servisi bir alan eklediğinde şemanın da güncellenmesi gerekir; güncellenmezse
"şemada olmayan alan" satırı testi düşürür. Bu, katı bir seçimdir ve bilinçlidir: sessiz
kalan bir denetim, olmayan bir denetimden ayırt edilemez.

## Özet

- Kayıt ve tekrar oynatma canlı servise hiç istek göndermez; alan adı değişikliği kayda
  karşı koşan testte görünmez, gerçek istekle koşan bir denetimde ilk çağrıda görünür.
- Şema, yanıtın alanlarını tür, desen, değer kümesi ve sınırla yazılı hâle getirir; dokuz
  alanlık bir kayıt için on altı kural yeterli oldu.
- Şema denetiminin yakaladığı sınıf yapısal uyuşmazlıktır: alanın yokluğu, tür değişimi,
  küme dışı değer. Tür kusuru testi kırmızıya çevirdi, kusur geri alınınca yeşile döndü.
- Şema denetiminin kaçırdığı sınıf anlamsal uyuşmazlıktır: kurallara uyan ama yanlış olan
  değer. İki alan arasındaki ilişkiyi ancak elle yazılmış bir değişmez görür, doğru değerin
  ne olduğunu ise ikisi de bilmez.
- Bedel: iki süreç, sınama başına en az bir istek, elle bakımı yapılan on altı kural; koşum
  süresinin neredeyse tamamı istek gidip gelmesinde geçiyor, denetimin kendisi ihmal edilebilir.

## Sonraki Adım

Bu dersin şeması katalog servisinin **ürettiği** biçimi tarif ediyor. Ödünç servisinin bu
alanların kaçını gerçekten okuduğu ise hiçbir yerde yazılı değil: dokuz alan denetlendi, oysa
karar işlevi dörde bakıyordu. Fark boşuna değil — sağlayıcı bir alanı kaldırmak istediğinde
"bunu kim okuyor" sorusunun yanıtı şemada yoktur. Sonraki ders beklentiyi tüketicinin
tarafından toplar: tüketicinin çalışan kodunun okuduğu alanlar kaydedilir, bu kayıttan bir
sözleşme üretilir ve sözleşme sağlayıcının takımında koşturulur. Ölçü, tüketicinin gerçekten
kullandığı alan sayısı ile sözleşmenin kapsadığı alan sayısı arasındaki farktır.
