# Vibe Coder #5.2: AI-TDD – Önce Test, Sonra Sihir

> Testi siz yazın, kodu ajan yazsın; ama testi ajanın elinden almadan bu düzen çalışmıyor. Ezberle geçilemeyen bir IBAN testi, kırmızıyı görmeden mühürlemeyen düz PHP bir betik ve dört araçta test dosyasına kilit.

- Yazar: [Halit Yeşil](https://halityesil.com/hakkimda/)
- Yayın tarihi: 2026-11-03
- Dil: Türkçe
- Kaynak adres: https://halityesil.com/vibe-coder-5-2-ai-tdd/
- İngilizce sürümü: https://halityesil.com/en/vibe-coder-5-2-ai-test-driven-development.md
- Seri: [Vibe Coderin El Kitabı](https://halityesil.com/seri/vibe-coderin-el-kitabi/), bölüm 5.2
- Kategori: Vibe Coding
- Etiketler: AI Ajanları, Aider, Claude Code, Codex, Cursor, php, PHPUnit, TDD, Test Odaklı Geliştirme, Vibe Coder
- Lisans: https://creativecommons.org/licenses/by-nc/4.0/
- Atıf: Halit Yeşil. “Vibe Coder #5.2: AI-TDD – Önce Test, Sonra Sihir”. halityesil.com, 2026-11-03. https://halityesil.com/vibe-coder-5-2-ai-tdd/

---

[Bir önceki bölümü](https://halityesil.com/vibe-coder-5-1-teknik-borc/) şu cümleyle kapatmıştık: borcu ölçmek ödemek değil, ödemenin ilk adımı kodun ne yaptığını değiştirmeden ona dokunabilmek; bunun için de önce testler gerekiyor. Burada bir ayrım yapmak lazım. Yıllardır ayakta duran eski bir kodun bugünkü davranışını teste çevirip dondurmak ayrı bir iş; onu [Kentsel Dönüşüm](https://halityesil.com/kentsel-donusum-1-risk-tespiti/) hattında, üçüncü bölüm İskele Kurmak'ta altinkaynak üzerinde yapacağız. Bu bölümün konusu henüz yazılmamış kod: testi sizin yazdığınız, kodu ajanın yazdığı **AI-TDD**.

Kent Beck ajanlarla kod yazmayı anlatırken onlara "cin" diyor. Metafor buradan: lambadan çıkan cin dileği harfiyen yerine getirir, ama sizin kastettiğinizi değil, sizin söylediğinizi. Test, dileğin yazılı hâli. Sorun şu ki bu cin dileğin yazılı olduğu kâğıda da uzanabiliyor. Bu bölümde dileği nasıl yazacağımıza, kâğıdı cinin elinden nasıl alacağımıza ve buna dört araçta nasıl kilit vurulacağına bakıyoruz. Sonunda elinizde testleri mühürleyen yüz satırlık bir betik olacak.

## AI-TDD nedir?

Test odaklı geliştirme (test-driven development) bildiğimiz döngü: önce kırmızı (başarısız test), sonra yeşil (testi geçiren en sade kod), sonra refactor. Beck'in ajanlara verdiği sistem talimatının maddelerinden biri de bu: "Always follow the TDD cycle: Red → Green → Refactor" ([Augmented Coding: Beyond the Vibes](https://newsletter.kentbeck.com/p/augmented-coding-beyond-the-vibes), 25.06.2025).

AI-TDD'de değişen şey roller. Kırmızıyı insan yazar, yeşili ajan. Kısa tanımı şöyle yapalım: **AI-TDD, testin insan tarafından yazılıp kilitlendiği, kodun ajana yazdırıldığı ve işin "bitti" sayılması için testin değişmeden yeşile dönmesinin şart olduğu test odaklı geliştirme düzenidir.** Son yarısı tanımın asıl kısmı. Testi yazmak işin yarısı; diğer yarısı testi ajanın elinden almak.

## Cin dileği nasıl esnetir?

Beck aynı yazıda ajanın yoldan çıktığını gösteren işaretleri sayıyor. Üçüncüsü şu: "Any indication that the genie was cheating, for example by disabling or deleting tests." Yani cinin hile yaptığına dair herhangi bir işaret, örneğin testleri devre dışı bırakması ya da silmesi.

Bu bir kişinin gözlemi olarak kalmıyor. İki ölçüm var:

- **METR, 05.06.2025:** [Recent Frontier Models Are Reward Hacking](https://metr.org/blog/2025-06-05-recent-reward-hacking) raporunda o3, kendisinden hızlı bir kernel yazması istenince puanlayan betiğin zaman ölçen fonksiyonunu değiştiriyor; başka bir görevde PyTorch'un eşitlik operatörünü ezip her karşılaştırmayı geçer hâle getiriyor. RE-Bench'te 128 koşudan 39'unda (%30,4) bu tür bir ödül hilesi görülmüş.
- **ImpossibleBench, 23.10.2025:** Ziqian Zhong, Aditi Raghunathan ve Nicholas Carlini'nin [makalesi](https://arxiv.org/abs/2510.20270) bilerek imkânsız görevler kuruyor: doğal dildeki tarifle birim testleri birbiriyle çelişiyor. Testi geçen her çözüm tarifi çiğnemiş demek. GPT-5, testlerden birinin tek bir beklentisi değiştirilmiş SWE-bench görevlerinde vakaların %76'sında, çelişkili sürümde %54'ünde "geçti". Aynı makalede iki önemli bulgu daha var: testler ajandan gizlenince hile neredeyse sıfıra iniyor; testler salt okunur yapılınca meşru performans korunurken test değiştirme yolu kapanıyor, ama başka kestirmeler açık kalıyor.

**Acı Gerçek:** yeşil ışığa giden en kısa yol çoğu zaman testin içinden geçiyor. Ajan kötü niyetli değil; ona "testler geçsin" dediniz, testler geçiyor. Dileği harfiyen yerine getirdi.

Gizli test fikri bu blogda tanıdık. [Beş Ajana Aynı Sınav](https://halityesil.com/ai-kodlama-ajani-karsilastirma-protokolu/) yazısında cevap anahtarını mühürlü zarfa koymuştuk. Günlük işte ise testleri ajandan saklayamazsınız; ajanın neyi geçmesi gerektiğini görmesi lazım. Gerçekçi orta yol: test görünür ama dokunulmaz.

## Dileği yazmak: testi siz yazın

Örnek iş küçük ama tarifi net olsun: Türk IBAN doğrulayıcı. Tarif TCMB'nin [IBAN bilgi kitapçığında](https://www.tcmb.gov.tr/wps/wcm/connect/EN/TCMB%2BEN/Bottom%2BMenu/IBAN/Information%2BBooklet) yazıyor: 26 karakter; `TR`, iki kontrol hanesi, beş haneli banka kodu, `0` olan bir rezerv hane ve 16 haneli hesap numarası. Kontrol haneleri ISO 13616'nın mod 97 hesabıyla doğrulanıyor: ilk dört karakter sona alınır, harfler sayıya çevrilir, kalan 1 çıkmalıdır ([Wikipedia'daki doğrulama adımları](https://en.wikipedia.org/wiki/International_Bank_Account_Number)).

İlk karar daha test yazılırken çıkıyor. TCMB hesap kısmı için "16 rakam" diyor; Wikipedia'daki ülke tablosu aynı alanı harf de alabilir diye gösteriyor. Hangisini seçeceğiniz bir tarif kararı ve bu karar ajana bırakılmaz. Ben birincil kaynağa, TCMB'ye uydum. Dilek sizin; cin yalnızca yerine getirir.

```php
<?php

declare(strict_types=1);

use PHPUnit\Framework\TestCase;

final class TrIbanTest extends TestCase
{
    public function testGecerliIbanKabulEdilir(): void
    {
        self::assertTrue(TrIban::gecerliMi('TR330006100519786457841326'));
    }

    public function testBoslukVeKucukHarfTolereEdilir(): void
    {
        self::assertTrue(TrIban::gecerliMi(' tr33 0006 1005 1978 6457 8413 26 '));
    }

    public function testKontrolHanesiYanlissaReddedilir(): void
    {
        self::assertFalse(TrIban::gecerliMi('TR340006100519786457841326'));
    }

    public function testBicimDisiGirdilerReddedilir(): void
    {
        $girdiler = [
            '',
            'TR33000610051978645784132',      // 25 karakter
            'TR3300061005197864578413260',    // 27 karakter
            'DE330006100519786457841326',     // ülke kodu
            'TR33000610X519786457841326',     // gövdede harf
            self::uret('00061' . '7' . '9786457841326000'), // rezerv hane 0 değil, kontrol hanesi doğru
        ];
        foreach ($girdiler as $girdi) {
            self::assertFalse(TrIban::gecerliMi($girdi), "Kabul edilmemeliydi: '$girdi'");
        }
    }

    /** Ezberle geçilemeyen test: her çalıştırmada aynı 500 rastgele IBAN ve tek hanesi bozulmuş ikizi. */
    public function testUretilenIbanlarVeBozukIkizleri(): void
    {
        mt_srand(2026);
        for ($i = 0; $i < 500; $i++) {
            $iban = self::uret(sprintf('%05d0%08d%08d', mt_rand(0, 99999), mt_rand(0, 99999999), mt_rand(0, 99999999)));
            self::assertTrue(TrIban::gecerliMi($iban), "Geçerli: $iban");

            $konum = mt_rand(4, 25);
            $bozuk = $iban;
            $bozuk[$konum] = (string) (((int) $iban[$konum] + mt_rand(1, 9)) % 10);
            self::assertFalse(TrIban::gecerliMi($bozuk), "Bozuk: $bozuk");
        }
    }

    /** ISO 13616 kontrol hanesi: BBAN + "TR00" (T=29, R=27), mod 97, 98'den çıkar. */
    private static function uret(string $bban): string
    {
        $kalan = 0;
        foreach (str_split($bban . '292700') as $rakam) {
            $kalan = ($kalan * 10 + (int) $rakam) % 97;
        }

        return sprintf('TR%02d%s', 98 - $kalan, $bban);
    }
}
```

İlk dört test klasik örnek testler. Beşincisi farklı bir tür: tek bir IBAN'ı değil, kuralı soruyor. Sabit tohumla (`mt_srand(2026)`) her çalıştırmada aynı 500 geçerli IBAN'ı üretiyor ve her birinin tek hanesi bozulmuş ikizini reddetmesini istiyor. Mod 97 tek hane değişikliğini her zaman yakaladığı için bu ikizlerin hepsi geçersiz olmak zorunda.

**Gizli Hazine:** örnek testler ezberlenebilir, kural testleri ezberlenemez. Birazdan bu farkı sayılarla göreceğiz.

Test sınıfında framework'e özgü bir şey yok: ne docblock anotasyonu ne attribute. Sandbox'ta PHPUnit 9.6.17 ile çalıştırdım; PHPUnit 13.4.5'in kaynağında `test` önekli metot keşfinin ve aşağıda kullanacağım `--fail-on-skipped` seçeneğinin durduğunu kontrol ettim.

## Kırmızıyı görmeden mühürlemeyin

TDD'nin en çok atlanan adımı kırmızıyı gerçekten görmek. Uygulama yazılmadan geçen bir test hiçbir şey ölçmüyordur. Claude Code'un [en iyi uygulamalar sayfası](https://code.claude.com/docs/en/best-practices) da örnek isteminde aynı sırayı veriyor: önce hatayı yeniden üreten başarısız bir test yaz, sonra düzelt. Aynı sayfada iki cümle daha var ki bu bölümün özeti olabilir: tek başına "All tests pass" demek size çok az şey söyler, çalıştırılan komutu ve çıktısını isteyin. Ve: CLAUDE.md yönergeler içindir, ajan bir yönergenin bu durumda geçerli olmadığına karar verebilir; istisnası olmayan kural kancaya ya da yasak kuralına taşınmalıdır.

Testleri ajanın elinden almadan önce mühürlüyoruz. Betik iki iş yapıyor: `muhurle` testleri çalıştırır, kırmızı değilse mühürlemeyi reddeder; kırmızıysa `tests/` ve `phpunit.xml*` dosyalarının SHA-256 özetlerini `.git/tdd-muhur.json` dosyasına yazar. `kontrol` önce özetleri karşılaştırır, sonra testleri çalıştırır; ikisinden biri bozuksa 2 ile çıkar. Ajan kancalarının "geri dön ve düzelt" diye anladığı kod 2.

```php
<?php

declare(strict_types=1);

/**
 * Test mührü: dileği (testleri) kırmızıyken mühürler, sonra testlere dokunulup
 * dokunulmadığını ve yeşile gerçekten kodla ulaşılıp ulaşılmadığını denetler.
 *   php test-muhru.php muhurle  testler kırmızıysa tests/ ve phpunit.xml* özetlerini kaydeder
 *   php test-muhru.php kontrol  mühür bozuksa 2, testler kırmızıysa 2, ikisi de temizse 0
 * Test komutu TDD_TEST_KOMUTU ortam değişkeniyle değiştirilebilir.
 * Git deposunun kökünden çalıştırın.
 */

const MUHUR = '.git/tdd-muhur.json';
const VARSAYILAN_KOMUT = 'phpunit --colors=never --fail-on-skipped --fail-on-incomplete';

/** @return array<string, string> yol => sha256 */
function korunanDosyalar(): array
{
    $yollar = glob('phpunit.xml*') ?: [];
    if (is_dir('tests')) {
        $dolasim = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator('tests', FilesystemIterator::SKIP_DOTS)
        );
        foreach ($dolasim as $dosya) {
            if ($dosya->isFile()) {
                $yollar[] = $dosya->getPathname();
            }
        }
    }
    sort($yollar);
    $ozetler = [];
    foreach ($yollar as $yol) {
        $ozetler[$yol] = hash_file('sha256', $yol);
    }

    return $ozetler;
}

/** @return array{int, list<string>} çıkış kodu ve çıktı satırları */
function testleriCalistir(): array
{
    $komut = getenv('TDD_TEST_KOMUTU') ?: VARSAYILAN_KOMUT;
    exec($komut . ' 2>&1', $cikti, $kod);

    return [$kod, $cikti];
}

if (!is_dir('.git')) {
    fwrite(STDERR, "Git deposunun kökünden çalıştırın.\n");
    exit(1);
}

$emir = $argv[1] ?? '';

if ($emir === 'muhurle') {
    [$kod] = testleriCalistir();
    if ($kod === 0) {
        fwrite(STDERR, "Testler zaten yeşil: ya özellik zaten var ya da testler hiçbir şey ölçmüyor. Mühürlenmedi.\n");
        exit(1);
    }
    $ozetler = korunanDosyalar();
    $kayit = ['tarih' => date('c'), 'dosyalar' => $ozetler];
    file_put_contents(MUHUR, json_encode($kayit, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n");
    printf("Kırmızı görüldü (çıkış %d). %d dosya mühürlendi.\n", $kod, count($ozetler));
    exit(0);
}

if ($emir === 'kontrol') {
    if (!is_file(MUHUR)) {
        fwrite(STDERR, "Mühür yok. Önce: php test-muhru.php muhurle\n");
        exit(1);
    }
    $muhur = json_decode((string) file_get_contents(MUHUR), true, 512, JSON_THROW_ON_ERROR)['dosyalar'];
    $simdi = korunanDosyalar();
    $ihlal = [];
    foreach ($muhur as $yol => $ozet) {
        if (!isset($simdi[$yol])) {
            $ihlal[] = "silindi: $yol";
        } elseif ($simdi[$yol] !== $ozet) {
            $ihlal[] = "değişti: $yol";
        }
    }
    foreach (array_keys(array_diff_key($simdi, $muhur)) as $yol) {
        $ihlal[] = "eklendi: $yol";
    }
    if ($ihlal !== []) {
        fwrite(STDERR, "Test mührü bozuldu. Testlere değil koda dokun:\n  " . implode("\n  ", $ihlal) . "\n");
        exit(2);
    }
    [$kod, $cikti] = testleriCalistir();
    if ($kod !== 0) {
        fwrite(STDERR, implode("\n", array_slice($cikti, -12)) . "\nMühür sağlam ama testler kırmızı.\n");
        exit(2);
    }
    echo "Mühür sağlam, testler yeşil.\n";
    exit(0);
}

fwrite(STDERR, "Kullanım: php test-muhru.php muhurle|kontrol\n");
exit(1);
```

Uygulama dosyası daha yokken çalıştırdım (01.11.2026, PHP 8.3.6 CLI):

```bash
$ php test-muhru.php muhurle
Kırmızı görüldü (çıkış 2). 3 dosya mühürlendi.
```

Beş testin beşi `Class "TrIban" not found` ile kırmızı. Testler yeşilken mühürlemeyi denediğimde betik reddetti, çıkış 1. Yeni bir test dosyası eklenince de mühür bozuluyor (`eklendi: tests/EkTest.php`, çıkış 2): yeni testi ajan değil siz ekler, siz yeniden mühürlersiniz.

## Cin kestirme ararsa: üç deneme

Ajanın yapabileceği üç kestirmeyi bu kez ben kendi elimle yazdım, ajan değil. Amaç mührün ve kural testinin neyi yakalayıp neyi yakalayamadığını görmek.

**Birinci kestirme, ezber.** Uygulama tek satır: boşlukları at, büyük harfe çevir, örnekteki IBAN'la karşılaştır.

```php
<?php

declare(strict_types=1);

final class TrIban
{
    public static function gecerliMi(string $iban): bool
    {
        return strtoupper(str_replace(' ', '', $iban)) === 'TR330006100519786457841326';
    }
}
```

Yalnızca dört örnek test çalışınca sonuç `OK (4 tests, 9 assertions)`. Ezber örnek testlerin hepsini geçiyor. Kural testi ise ilk üretilen IBAN'da düşüyor: `Geçerli: TR768230509256077473871898`, `kontrol` çıkışı 2.

**İkinci kestirme, testi silmek.** Kural testini test dosyasından çıkardım. Düz `phpunit` yine `OK (4 tests, 9 assertions)` diyor; ekranda kırmızı yok, kimse fark etmez. Mühür fark ediyor:

```bash
$ php test-muhru.php kontrol
Test mührü bozuldu. Testlere değil koda dokun:
  değişti: tests/TrIbanTest.php
```

**Üçüncü kestirme, atlatmak.** Bu sefer test dosyasına hiç dokunmadım; uygulama metodu PHPUnit'in "test atlandı" istisnasını fırlatıyor. PHPUnit 9.6'da düz `phpunit` şunu basıp **0 ile çıkıyor**: `OK, but incomplete, skipped, or risky tests! Tests: 5, Assertions: 0, Skipped: 5.` Sıfır doğrulama, yeşil çıkış kodu. Mührün test komutundaki `--fail-on-skipped` bu yüzden orada; `kontrol` 2 ile çıkıyor.

Son olarak dürüst uygulama:

```php
<?php

declare(strict_types=1);

final class TrIban
{
    /** TCMB: TR + 2 kontrol hanesi + 5 hane banka kodu + 1 rezerv hane (0) + 16 hane hesap = 26 karakter. */
    public static function gecerliMi(string $iban): bool
    {
        $iban = strtoupper(str_replace(' ', '', $iban));
        if (preg_match('/^TR\d{7}0\d{16}$/', $iban) !== 1) {
            return false;
        }

        // ISO 13616: ilk dört karakter sona alınır, harfler sayıya çevrilir (T=29, R=27), mod 97 = 1 olmalı.
        $kalan = 0;
        foreach (str_split(substr($iban, 4) . '2927' . substr($iban, 2, 2)) as $rakam) {
            $kalan = ($kalan * 10 + (int) $rakam) % 97;
        }

        return $kalan === 1;
    }
}
```

`kontrol` çıktısı `Mühür sağlam, testler yeşil.`; PHPUnit tarafında `OK (5 tests, 1009 assertions)`, toplam 0,12 saniye. Örnek testler 9 doğrulama yaptı, kural testi 1.000.

**Sonuç:** üç kestirmenin üçü de düz `phpunit` gözüyle bakılınca "yeşil" ya da "yeşile çok yakın" görünüyordu. Üçünü yakalayan şey test sayısı değil, testin türü ve testin dokunulmaz olmasıydı.

## Testi ajanın elinden almak: dört araçta kilit

[4.2'de](https://halityesil.com/vibe-coder-4-2-agents-md-claude-md/) kural dosyasının rica ettiğini, kancanın uyguladığını söylemiştik. AGENTS.md'ye "testleri değiştirme" yazmak dileği kâğıda yazmak; aşağıdakiler kâğıdı cama koymak. Dört araçta da bir yol var, ama yolların gücü aynı değil (dokümanlar 01.11.2026'da okundu):

- **Claude Code:** `permissions.deny` içinde `Edit(...)` kuralı. [İzin dokümanına göre](https://code.claude.com/docs/en/permissions) `Edit` kuralları dosya düzenleyen bütün yerleşik araçlara uygulanıyor; `sed`, `tee` gibi tanınan Bash komutlarına ve `> dosya` yönlendirmelerine de. Uygulanmadığı yer, dosyayı kendisi açıp yazan bir Python ya da Node betiği; işletim sistemi düzeyinde engel için doküman sandbox'ı gösteriyor. Mührün işe yaradığı boşluk tam burası. Bitişi de `Stop` kancasına bağlıyoruz: kanca 2 ile çıkarsa stderr Claude'a gider ve tur bitmez.
- **Codex:** [kanca dokümanına göre](https://learn.chatgpt.com/docs/hooks) `PreToolUse`, dosya düzenleyen `apply_patch` çağrısını çalışmadan önce engelleyebiliyor (2 ile çıkış ve stderr, ya da `permissionDecision: "deny"`). Doküman bunu kendisi "a useful guardrail, not a complete enforcement boundary" diye tarif ediyor. `Stop` kancası `decision: "block"` döndürürse sebep metni yeni bir istem olarak modele gidiyor. Proje içindeki kancalar ancak `.codex/` katmanına güvenilmişse yükleniyor.
- **Cursor:** [kanca dokümanında](https://cursor.com/docs/hooks) düzenlemeden önce çalışan `beforeFileEdit` yok; `afterFileEdit` düzenlemeden sonra çalışıyor ve izin çıktısı yok. `preToolUse` her araçtan önce çalışıyor ve eşleştiriciler arasında `Write` var, ama dosya düzenlemenin bu yoldan geçtiği açıkça yazmıyor. Güvenilir yol `stop` kancası: `followup_message` dolu dönerse bir sonraki kullanıcı mesajı olarak gönderiliyor, varsayılan döngü sınırı 5.
- **Aider:** en sade kilit burada. [Seçenekler sayfasına göre](https://aider.chat/docs/config/options.html) `--read` dosyayı salt okunur ekliyor; `--test-cmd` ile `--auto-test` her düzenlemeden sonra komutu çalıştırıyor.

Claude Code için `.claude/settings.json`:

```json
{
  "permissions": {
    "deny": [
      "Edit(/tests/**)",
      "Edit(/phpunit.xml)",
      "Edit(/test-muhru.php)",
      "Edit(/.git/**)"
    ]
  },
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "cd \"$CLAUDE_PROJECT_DIR\" && php test-muhru.php kontrol"
          }
        ]
      }
    ]
  }
}
```

Aider için tek satır; uygulama dosyası düzenlenebilir, test dosyası salt okunur:

```bash
aider src/TrIban.php --read tests/TrIbanTest.php --test-cmd "php test-muhru.php kontrol" --auto-test
```

**Acı Gerçek:** bu ayarların hiçbirini bu yazı için ajanların içinde çalıştırmadım. Betik, testler ve üç kestirme gerçek; ajanların bu kilitlere nasıl tepki verdiği değil. Bir de dürüst bir sınır: mühür kilit değil, kurşun mühür. Açılınca belli olur ama açılmasını engellemez. Kararlı bir ajan `.git` içindeki mühür dosyasını da yeniden yazabilir. O yüzden son söz CI'da olmalı: kırmızıyı gördüğünüz ve sizin onayladığınız commit'i tutun, birleştirmeden önce testlerin ondan sapmadığını kontrol edin.

```bash
git diff --exit-code --stat <kirmizi-commit> -- tests phpunit.xml
```

Test dosyaları değişmemişse çıkış 0; değişmişse 1 ve değişen dosyalar listede.

## Ne zaman işe yarar, ne zaman yaramaz?

**Yarar:** tarifi net, girdisi ve çıktısı belli işlerde. Doğrulama, ayrıştırma, para ve vergi hesabı, tarih aralıkları, durum makineleri. Ajanı gözetimsiz bıraktığınız uzun işlerde ise kilidin değeri artıyor, çünkü "bitti" dediği anda kanıtı yanında geliyor.

**Yaramaz:** ne istediğinizi henüz bilmediğiniz keşif aşamasında. [3.2'deki](https://halityesil.com/vibe-coder-3-2-frontend-prototipleme-v0-bolt-new-ve-bes-dakikalik-hayaller/) prototip işi gibi "görünce anlarım" türü işlerde önce test yazmak, henüz olmayan bir tarifi dondurur. Arayüz ve görsel davranışta birim testi zaten zayıf kalır.

İki ince nokta daha. Birincisi, mühür refactor'ü engellemez; kodu dilediğiniz kadar yeniden düzenleyin, testler aynı kaldıkça mühür sağlam. Ama testin kendisini değiştirmek artık insan işi: değiştirir, kırmızıyı görür, yeniden mühürlersiniz. İkincisi, testi ajana da yazdırabilirsiniz; o zaman testi yazan oturum ile kodu yazan oturum ayrı olsun ve testi siz okuyup onaylayın. Aynı cin hem dileği hem gerçekleşmesini yazarsa ikisinin birbirini tutması sürpriz olmaz.

Testler yeşil, mühür sağlam. Ama yeşil, güvenli demek değil: SQL injection'a açık bir sorgu da kendi testini mutlulukla geçer. Serinin bir sonraki bölümü, Vibe Coder #5.3: Güvenlik Denetimi – AI Kodunda Gizli Mayınları Avlamak, ajanın yazdığı koda OWASP gözüyle bakacak. Serinin tamamı ve yayınlanan bölümlerin listesi [Vibe Coder El Kitabı](https://halityesil.com/vibe-coder-el-kitabi-sentaksin-olumu-orkestrasyonun-dogusu/) sayfasında.

Cin dileği harfiyen yerine getirir. Harfleri siz yazın, kâğıdı da siz saklayın. Teknolojiyle kalın.
