---
title: 'Sahte Sunucularla Geliştirme'
source: 'https://academia.sh/tr/kurslar/tumlesim-testi/sahte-sunucularla-gelistirme'
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'
---

# Sahte Sunucularla Geliştirme

Sağlayıcı hazır olmadan geliştirmeyi sürdüren sahte sunucunun gerçeğinden saptığı noktaların sayılması: aynı istek kümesinin iki sunucuya sürülmesi, sözleşme koşumunun kapattığı sapmanın ölçülmesi ve istekler arası duruma bakan sapmanın hiçbir sözleşmeyle kapatılamaması.

Önceki üç denetim de sağlayıcının hazır olduğunu varsaydı. Oysa sözleşmenin en çok işe
yaradığı an, sağlayıcının **henüz yazılmadığı** andır: ödünç servisini geliştiren takım
katalog servisinin bitmesini beklemek zorunda değildir, çünkü sözleşme bellidir.

**Sahte sunucu** (mock server) sözleşmeyi çalıştırılabilir hâle getirir ve bekleme süresini
kaldırır. Karşılığında bir risk üretir: gerçeğin yerine geçen şey gerçeğin kendisi değildir.
Bu ders o riski bir sayıya çevirir — sahte sunucu gerçeğinden **kaç noktada sapıyor** ve bu
sapmaların kaçını buraya kadar kurulan sözleşme koşumu kapatabiliyor.

## İki Sunucu, Tek Sözleşme

Sözleşme önceki derslerin ürettiği bilgidir: ödünç servisinin okuduğu beş alan, tipleriyle
birlikte. Sahte sunucunun üreteceği örnek değerler de aynı dosyada durur.

```js
// sozlesme.mjs — kitap yanitinin sozlesmesi: okunan alanlar, tipleri ve ornek degerler
export const ALAN = { isbn: "dizgi", baslik: "dizgi", raftaAdet: "tamsayi", durum: "dizgi", oduncGun: "tamsayi" };
export const ORNEK = { isbn: "978-0262033848", baslik: "Ornek Kitap", raftaAdet: 2, durum: "rafta", oduncGun: 14 };
export const ORNEK_V1 = { ...ORNEK, raftaAdet: "2" };   // sahte sunucunun ilk surumu: ornek deger dizgi
export const ETKILESIM = [{ yol: "/kitap/978-0262033848", alanlar: ALAN }];
```

Gerçek katalog servisi iki kitap taşıyor, ayırma isteğinde raf adedini düşürüyor ve rafta
kitap yoksa isteği reddediyor.

```js
// gercek.mjs — gercek katalog servisi: durum tutar, is kuralini uygular
// Kullanim: node gercek.mjs <port|0>
import { createServer } from "node:http";
if (process.argv[2] === undefined) { console.log("kullanim: node gercek.mjs <port|0>"); process.exit(0); }

const KITAP = {
  "978-0262033848": { isbn: "978-0262033848", baslik: "Algoritmalara Giris", raftaAdet: 2,
    durum: "rafta", oduncGun: 14, etiketler: ["algoritma"] },
  "978-0201896831": { isbn: "978-0201896831", baslik: "Basvuru El Kitabi", raftaAdet: 0,
    durum: "oduncte", oduncGun: 7, etiketler: [] },
};
const govdeOku = (i) => new Promise((c) => { let v = ""; i.on("data", (p) => (v += p)); i.on("end", () => c(v)); });

const s = createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  const yol = istek.url.split("?")[0];
  const json = (kod, g) => {
    yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8" });
    yanit.end(JSON.stringify(g));
  };
  if (istek.method === "POST" && yol === "/ayirma") {
    const k = KITAP[JSON.parse((await govdeOku(istek)) || "{}").isbn];
    if (k === undefined) return json(404, { hata: "kitap yok" });
    if (k.raftaAdet < 1) return json(409, { hata: "rafta bos" });
    k.raftaAdet -= 1;
    return json(201, { ayirmaNo: "A-1", isbn: k.isbn });
  }
  const k = KITAP[yol.replace("/kitap/", "")];
  return k ? json(200, k) : json(404, { hata: "kitap yok" });
});
s.listen(Number(process.argv[2]), "127.0.0.1", () => console.log(`gercek ${s.address().port}`));
```

Sahte sunucu sözleşmeden üretilir ve sözleşmede yazılı olan her şeyi yapar: örnek gövdeyi
döndürür. Sözleşmede yazılı olmayan hiçbir şeyi yapmaz — durum tutmaz, kural uygulamaz.

```js
// sahte.mjs — sozlesmeden uretilen sahte sunucu: durum tutmaz, is kurali uygulamaz
// Kullanim: SAHTE_SURUM=1|2 node sahte.mjs <port|0>
import { createServer } from "node:http";
import { ORNEK, ORNEK_V1 } from "./sozlesme.mjs";
if (process.argv[2] === undefined) { console.log("kullanim: node sahte.mjs <port|0>"); process.exit(0); }

const GOVDE = process.env.SAHTE_SURUM === "2" ? ORNEK : ORNEK_V1;
const s = createServer((istek, yanit) => {
  istek.resume();
  yanit.sendDate = false;
  const ayirma = istek.url.split("?")[0] === "/ayirma";
  yanit.writeHead(ayirma ? 201 : 200, { "content-type": "application/json; charset=utf-8" });
  yanit.end(JSON.stringify(ayirma ? { ayirmaNo: "A-1", isbn: GOVDE.isbn } : GOVDE));
});
s.listen(Number(process.argv[2]), "127.0.0.1", () => console.log(`sahte ${s.address().port}`));
```

## Sapma Noktalarının Sayımı

Sapma bir görüş değil, bir sayıdır: aynı istek kümesi iki sunucuya da sürülür ve beş ölçüt
karşılaştırılır.

```js
// sapma.mjs — ayni istek kumesi iki sunucuya surulur, sapma noktalari sayilir
import { spawn } from "node:child_process";
import { ALAN } from "./sozlesme.mjs";

const ISBN = "978-0262033848";
const tipAdi = (d) => (Number.isInteger(d) ? "tamsayi" : typeof d === "string" ? "dizgi" : "diger");

async function surucu(dosya) {
  const surec = spawn("node", [dosya, "0"], { env: process.env });
  const port = await new Promise((c) => surec.stdout.once("data", (v) => c(String(v).split(" ")[1])));
  const taban = `http://127.0.0.1:${port}`;
  const al = async (yol, secenek) => {
    const c = await fetch(taban + yol, secenek);
    return { kod: c.status, govde: await c.json() };
  };
  const kitap = await al(`/kitap/${ISBN}`);
  const yok = await al("/kitap/978-0000000000");
  const bos = await al("/ayirma", { method: "POST", body: JSON.stringify({ isbn: "978-0201896831" }) });
  await al("/ayirma", { method: "POST", body: JSON.stringify({ isbn: ISBN }) });
  const sonra = await al(`/kitap/${ISBN}`);
  surec.kill();
  return {
    "basari alan sayisi": String(Object.keys(kitap.govde).length),
    "raftaAdet tipi": tipAdi(kitap.govde.raftaAdet),
    "bilinmeyen kitap kodu": String(yok.kod),
    "rafta bos kitapta ayirma": String(bos.kod),
    "ayirma sonrasi raftaAdet": String(sonra.govde.raftaAdet),
  };
}

const sahte = await surucu("sahte.mjs");
const gercek = await surucu("gercek.mjs");
console.log(`${"olcut".padEnd(26)}${"sahte".padStart(9)}${"gercek".padStart(9)}${"sapma".padStart(7)}`);
let sapma = 0;
for (const olcut of Object.keys(sahte)) {
  const fark = sahte[olcut] !== gercek[olcut];
  sapma += fark ? 1 : 0;
  console.log(`${olcut.padEnd(26)}${sahte[olcut].padStart(9)}${gercek[olcut].padStart(9)}` +
    `${(fark ? "evet" : "hayir").padStart(7)}`);
}
console.log(`sahte surum ${process.env.SAHTE_SURUM ?? "1"}: sapma ${sapma}/${Object.keys(sahte).length}, ` +
  `sozlesmenin denetledigi alan ${Object.keys(ALAN).length}`);
```

```
olcut                         sahte   gercek  sapma
basari alan sayisi                5        6   evet
raftaAdet tipi                dizgi  tamsayi   evet
bilinmeyen kitap kodu           200      404   evet
rafta bos kitapta ayirma        201      409   evet
ayirma sonrasi raftaAdet          2        1   evet
sahte surum 1: sapma 5/5, sozlesmenin denetledigi alan 5
```

Beş ölçütün beşi de sapıyor. Sapmalar aynı türden değildir ve ayrımı bu dersin ekseni.
İlk ikisi **biçim** sapmasıdır: alan sayısı ve tip. Üçüncü ve dördüncü **karar** sapmasıdır:
sahte sunucu her isteğe olumlu yanıt verir, çünkü hangi durumda ne döneceğini bilmez.
Beşincisi **durum** sapmasıdır: gerçek servis ayırmadan sonra raf adedini düşürür, sahte
sunucu iki isteği birbirinden bağımsız görür.

## Sözleşme Koşumunun Kapattığı Sapma

Sahte sunucu da sözleşme koşumundan geçmelidir. Aynı sınama dosyası hem sahteye hem gerçeğe
karşı koşturulur; sunucu adı dışarıdan verilir.

```js
// sozlesme.test.mjs — ayni sozlesme kosumu hem sahte hem gercek sunucuya karsi
// Kullanim: SUNUCU=sahte.mjs|gercek.mjs SAHTE_SURUM=1|2 node --test sozlesme.test.mjs
import { test, before, after } from "node:test";
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { ETKILESIM } from "./sozlesme.mjs";

const DOSYA = process.env.SUNUCU ?? "gercek.mjs";
const tipAdi = (d) => (Number.isInteger(d) ? "tamsayi" : typeof d === "string" ? "dizgi" : "diger");
let surec, taban;

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

for (const e of ETKILESIM) {
  test(`${DOSYA} surum ${process.env.SAHTE_SURUM ?? "-"} ${e.yol}`, async () => {
    const govde = await (await fetch(taban + e.yol)).json();
    const eksik = Object.entries(e.alanlar)
      .filter(([ad, tip]) => govde[ad] === undefined || tipAdi(govde[ad]) !== tip)
      .map(([ad, tip]) => `${ad} (${tip} bekleniyordu)`);
    assert.equal(eksik.length, 0, `karsilanmayan alan: ${eksik.join(", ")}`);
  });
}
```

```bash
for s in "sahte.mjs 1" "sahte.mjs 2" "gercek.mjs -"; do
  set -- $s
  SUNUCU=$1 SAHTE_SURUM=$2 node --test --test-reporter=tap sozlesme.test.mjs |
    grep -E '^(ok|not ok)|karsilanmayan' | sed 's/^ *//'
done
SAHTE_SURUM=2 node sapma.mjs | tail -1
```

```
not ok 1 - sahte.mjs surum 1 /kitap/978-0262033848
karsilanmayan alan: raftaAdet (tamsayi bekleniyordu)
ok 1 - sahte.mjs surum 2 /kitap/978-0262033848
ok 1 - gercek.mjs surum - /kitap/978-0262033848
sahte surum 2: sapma 4/5, sozlesmenin denetledigi alan 5
```

**Yakalanan hata sınıfı, sahte sunucunun sözleşmeden sapmasıdır.** Sahte sunucunun ilk
sürümünde örnek değer dizgi olarak yazılmıştı; sözleşme koşumu kırmızıya döndü ve alanı
adıyla bildirdi. Örnek düzeltildikten sonra aynı koşum yeşile döndü ve sapma sayısı beşten
dörde indi. Buradan çıkan kural tek cümledir: **sahte sunucu, tüketicinin sınandığı
sözleşmenin aynısıyla sınanmalıdır**; yoksa tüketici sözleşmeye değil sahte sunucunun
kendisine göre geliştirilmiş olur.

**Kaçan hata sınıfı, sözleşmenin görüş alanı dışındaki sapmalardır ve üçe ayrılır.**
Birincisi alan sayısı: gerçek servis etiket alanını da döndürüyor, sahte sunucu
döndürmüyor. Sözleşme onu kapsamıyor, çünkü tüketici okumuyor; bir gün okumaya başlarsa
önceki dersin kayıt düzeni onu sözleşmeye ekler ve sapma kendiliğinden kapanır.

İkincisi karar sapması: bilinmeyen kitapta durum kodu ve rafta kitap yokken ayırma isteği.
Bunlar sözleşmeye **yeni etkileşim** eklenerek kapatılabilir; bedeli her biri için bir
senaryo ve sahte sunucuya bir kural yazmaktır. Sahte sunucu her kapatılan sapmada gerçek
servise biraz daha benzer; sınırda sahte sunucu gerçek servisin ikinci bir uygulamasına
dönüşür ve o noktada var olma nedenini yitirir.

Üçüncüsü kapatılamaz. Ayırmadan sonra raf adedinin düşmesi tek bir isteğin özelliği
değildir; **iki isteğin arasındaki ilişkidir**. Sözleşme testinin baktığı birim bir istek ve
onun yanıtıdır; istekler arası durum bu birimin dışında kalır. Sahte sunucuya durum
tutturmak bu sapmayı kapatmaz, yalnız yerini değiştirir — bu kez sahte sunucunun durum
kuralı gerçeğinden sapabilir ve onu sınayacak bir sözleşme yoktur.

## Sahte Sunucunun Bedeli

```js
// bedel.mjs — sahte sunucunun bedeli: kac satir duzenek, kac istek, hangi sure
import { spawn } from "node:child_process";
import { readFileSync } from "node:fs";

const satir = (d) => readFileSync(d, "utf8").trimEnd().split("\n").length;

async function olc(dosya) {
  const surec = spawn("node", [dosya, "0"], { env: { ...process.env, SAHTE_SURUM: "2" } });
  const port = await new Promise((c) => surec.stdout.once("data", (v) => c(String(v).split(" ")[1])));
  const yol = `http://127.0.0.1:${port}/kitap/978-0262033848`;
  for (let i = 0; i < 20; i += 1) await (await fetch(yol)).json();      // isinma
  const t = performance.now();
  for (let i = 0; i < 100; i += 1) await (await fetch(yol)).json();
  const sure = performance.now() - t;
  surec.kill();
  return sure;
}

const s = await olc("sahte.mjs");
const g = await olc("gercek.mjs");
console.log(`duzenek: sahte.mjs ${satir("sahte.mjs")} satir, gercek.mjs ${satir("gercek.mjs")} satir`);
console.log(`sapma kosumu 2 surec ve 10 istek, sozlesme kosumu 1 surec ve 1 etkilesim`);
console.log(`iki sunucunun sureleri ayni buyukluk sirasinda : ${s / g > 0.33 && s / g < 3}`);
console.log(`sahte sunucu kosumu gercegin yarisindan kisa   : ${s < g / 2}`);
```

```
duzenek: sahte.mjs 15 satir, gercek.mjs 31 satir
sapma kosumu 2 surec ve 10 istek, sozlesme kosumu 1 surec ve 1 etkilesim
iki sunucunun sureleri ayni buyukluk sirasinda : true
sahte sunucu kosumu gercegin yarisindan kisa   : false
```

Son satır bir yanlış anlamayı düzeltiyor. Sahte sunucu **hız** için kullanılmaz: iki sunucu
da yerel bir süreçtir ve yüz isteğin süresi aynı büyüklük sırasındadır. Sahte sunucunun
sağladığı şey süre değil **zamanlamadır** — sağlayıcı hazır olmadan tüketicinin
geliştirilebilmesi. Bu ayrım, sahte sunucunun gerçek servis yerine sürekli kullanılmasının
gerekçesiz olduğunu da söyler.

Koşumdan bağımsız bedel iki sayıdadır. Düzenek olarak sahte sunucu on beş satır, gerçek
servis otuz bir satır; aradaki on altı satır tam olarak sahte sunucunun **yapmadığı**
işlerdir ve sapma tablosundaki beş satırın karşılığıdır. İkinci sayı sapma tablosunun
kendisidir: beş ölçüt, koşum başına iki süreç ve on istek. Bu tablo bir kere yazılır ve
sözleşme her değiştiğinde koşturulur; sahte sunucunun gerçekten ne kadar saptığını tahmin
etmenin başka yolu yoktur.

## Özet

- Sahte sunucu sözleşmeyi çalıştırılabilir hâle getirir ve tüketicinin sağlayıcıyı
  beklemeden geliştirilmesini sağlar; karşılığında gerçeğinden sapma riski üretir.
- Sapma bir sayıdır: aynı istek kümesi iki sunucuya sürüldüğünde beş ölçütün beşi de saptı.
- Sapmalar üç türdür — biçim (alan sayısı, tip), karar (durum kodu, kural), durum (istekler
  arası etki).
- Sahte sunucu tüketiciyle aynı sözleşmeye karşı sınandığında biçim sapması yakalandı;
  örnek değer düzeltilince koşum yeşile döndü ve sapma beşten dörde indi.
- Karar sapmaları sözleşmeye etkileşim eklenerek kapatılabilir; her kapatma sahte sunucuyu
  gerçeğe yaklaştırır ve sınırda onu ikinci bir uygulamaya dönüştürür.
- Durum sapması kapatılamaz: sözleşme testinin birimi tek bir istek ve yanıtıdır, istekler
  arası ilişki bu birimin dışında kalır. Bedel: 2 süreç, 10 istek, on beş satırlık düzenek;
  kazanç süre değil zamanlamadır.

## Sonraki Adım

Bu konu dört denetim kurdu ve dördü de aynı biçimdeydi: **tek bir sınıra** bakmak. Şema
denetimi bir yanıtı inceledi, sözleşme testi bir tüketici ile bir sağlayıcı arasındaki
alanları saydı, uyumluluk denetimi iki şema metnini karşılaştırdı, sapma tablosu bir sahte
sunucu ile bir gerçek servisi ölçtü. Her biri kendi sınırında güçlüdür ve hepsi birlikte
koştuğunda bile bir soru yanıtsız kalır.

Üyenin raftaki kitabı arayıp ayırttığı ve ödünç aldığı yol, bu denetimlerin hiçbirinin
kapsamında değildir. O yol birden çok isteği, birden çok servisi ve istekler arasında
taşınan durumu birlikte kullanır; son ölçütün kapatılamaz çıkması bunun ilk işaretiydi.
Sonraki konu bu yolu bir bütün olarak koşturan testleri ele alır ve ilk sorusu bir maliyet
sorusudur: aynı hatayı üç ayrı seviyede yakalamak neye mal olur, hangi seviyede kaç test
tutmak gerekir.
