İçeriğe geç
academia.sh

Ders 21 / 21

Test

Özellik testi ve birim testi, küçük bir doğrulama fonksiyonu; sahte kuyrukta işin kaydedilip koşmaması, kuyruk boşaltılınca işin koşması ve sahte kuyruğun işin gövdesini ölçmemesi.

Yayın Güncelleme

Bu derste

  • İstekten yanıta giden bir özellik testini küçük bir doğrulama fonksiyonuyla yazar
  • Sahte kuyrukta işin kaydedildiğini ama koşmadığını ölçer
  • Özellik testinin geçtiği yerde işin gövdesini birim testiyle ayrıca sınar
İçindekiler

Yerelleştirme dersinde yedek dil, eksik bir çeviriyi hatasız bir yanıtın arkasına sakladı. Önceki derslerde de aynı şey yaşandı: kuyruğa konan iş yanıttan sonra koşar, yanıt ise işi beklemeden döner. Peki testin içinde, gerçek bir işçi yokken, işin koştuğunu nasıl bilebiliriz? Bu ders şunu sorar: bir test işin kaydedildiğini doğruluyorsa, işin koştuğunu da doğrulamış olur mu?

Özellik testi (feature test) bir isteği uygulamaya verir ve yanıtı, yanında bırakılan izleri doğrular. Birim testi (unit test) tek bir sınıfı ya da fonksiyonu, çevresinden ayırarak sınar. Sahte nesne (fake) gerçek bir bağımlılığın yerine konan ve yaptıklarını kaydeden basit bir benzeridir: bu derste sahte kuyruk işi çalıştırmaz, yalnız saklar.

US12. Test kitaplığı kullanılmaz. Ders, Test sınıfında iki fonksiyonlu bir doğrulayıcı kurar: dogrula() koşul yanlışsa bir hata fırlatır, calistir() bir testi çalıştırıp geçti ya da kaldı olarak yazar. Eşitlik, dizi karşılaştırması, ayrıntılı fark çıktısı ve test keşfi yoktur; her test elle çağrılır.

US13. Kuyruk gerçek değildir: SahteKuyruk işi bir diziye koyar ve isle() çağrılana kadar koşturmaz. koy() işi ertele() ile işaretler, isle() ise her işi kostu() ile işaretleyip çalıştırır. Gerçek bir sürücü, yeniden deneme, gecikme ve hata yönetimi modellenmez. İş küçük bir hesaptır: genişliği dörde bölen KucukResimIsi ve bilerek yanlış çalışan HataliKucukResimIsi.

Ölçüm Çekirdeği

Kurs boyunca her derste aynı ölçüm kullanılır. kostu.php beklenen bir parçanın bu istekte koşup koşmadığını kaydeder; dosya her derste birebir verilir ve çıktı bloğu yoktur, yalnız diske yazılır. Gunluk üç liste tutar: isteğin yoluna konanlar, istek içinde çağrılanlar ve isteğin dışına bırakılanlar. sinifla() bunlara sırayla bakar. Karar sırası sinifla()’nın kendisidir: koştu, ertelendi, atlandı, bağlanmadı. Parçayı geliştirici adlandırır; ölçülen, geliştiricinin bu istekte koşmasını beklediği parçalardır. Bu derste ölçülen parça kucukresim işidir: iş kaydedilince yola() ve ertele(), kuyruk boşaltılınca kostu() çağrılır.

<?php
// kostu.php — beklenen bir parça bu istekte koştu mu?
declare(strict_types=1);

final class Gunluk {
    public static array $yol = [];         // isteğin yoluna konan parçalar
    public static array $kostu = [];       // istek içinde çağrılanlar
    public static array $ertelenen = [];   // isteğin dışına bırakılanlar
    public static function yeniIstek(): void { self::$yol = self::$kostu = self::$ertelenen = []; }
    public static function yola(string $parca): void { self::$yol[] = $parca; }
    public static function kostu(string $parca): void { self::$kostu[] = $parca; }
    public static function ertele(string $parca): void { self::$ertelenen[] = $parca; }
}

function sinifla(string $parca): string {
    return match (true) {
        in_array($parca, Gunluk::$kostu, true) => 'koştu',
        in_array($parca, Gunluk::$ertelenen, true) => 'ertelendi',
        in_array($parca, Gunluk::$yol, true) => 'atlandı',
        default => 'bağlanmadı',
    };
}

Sınanan Kod

uygulama.php sınanan her şeyi tutar: Is arayüzü, iki iş, SahteKuyruk ve YuklemeDenetleyicisi. Denetleyici bir genişlik alır, işi kuyruğa koyar ve 201 ile döner. Hangi iş sınıfının kullanılacağı kurucuya verilir; varsayılan doğru olandır. test.php ise doğrulayıcıdır.

<?php
// uygulama.php — sınanan kod: iş arayüzü, sahte kuyruk ve yükleme denetleyicisi
declare(strict_types=1);
require_once 'kostu.php';

interface Is {
    public function ad(): string;
    public function calistir(): int;
}

final class KucukResimIsi implements Is {
    public function __construct(private int $genislik) {}
    public function ad(): string { return 'kucukresim'; }
    public function calistir(): int { return intdiv($this->genislik, 4); }
}

final class HataliKucukResimIsi implements Is {
    public function __construct(private int $genislik) {}
    public function ad(): string { return 'kucukresim'; }
    public function calistir(): int { return $this->genislik * 4; }   // yanlış yön
}

final class SahteKuyruk {
    public array $isler = [];
    public function koy(Is $is): void {
        $this->isler[] = $is;
        Gunluk::ertele($is->ad());
    }
    public function isle(): array {
        $sonuclar = [];
        foreach ($this->isler as $is) {
            Gunluk::kostu($is->ad());
            $sonuclar[] = $is->calistir();
        }
        $this->isler = [];
        return $sonuclar;
    }
}

final class YuklemeDenetleyicisi {
    public function __construct(private SahteKuyruk $kuyruk, private string $isSinifi = KucukResimIsi::class) {}
    public function yukle(int $genislik): array {
        Gunluk::yeniIstek();
        Gunluk::yola('kucukresim');
        $this->kuyruk->koy(new ($this->isSinifi)($genislik));
        return [201, 'yuklendi'];
    }
}
<?php
// test.php — kitaplık yok: iki fonksiyonlu küçük bir doğrulayıcı
declare(strict_types=1);

final class Test {
    public static int $gecen = 0;
    public static int $kalan = 0;

    public static function dogrula(bool $kosul, string $mesaj): void {
        if (!$kosul) {
            throw new RuntimeException($mesaj);
        }
    }

    public static function calistir(string $ad, callable $test): void {
        try {
            $test();
            self::$gecen++;
            $durum = 'gecti';
        } catch (RuntimeException $hata) {
            self::$kalan++;
            $durum = 'kaldi — ' . $hata->getMessage();
        }
        printf("%-28s %s\n", $ad, $durum);
    }

    public static function ozet(): void {
        printf("toplam: %d gecti, %d kaldi\n", self::$gecen, self::$kalan);
    }
}

Özellik Testi: İş Kaydedildi, Koşmadı

ozellik.php isteği denetleyiciye verir ve üç şeyi doğrular: yanıt 201 mü, iş kuyruğa girdi mi, iş yanıt sırasında koşmadı mı. Son satır kucukresim parçasının sınıfını yazar.

<?php
// ozellik.php — özellik testi: istekten yanıta, sahte kuyrukla
require_once 'uygulama.php';
require_once 'test.php';

$kuyruk = new SahteKuyruk();
$yanit = (new YuklemeDenetleyicisi($kuyruk))->yukle(800);

Test::calistir('yanit 201 doner', fn() => Test::dogrula($yanit[0] === 201, "durum {$yanit[0]}"));
Test::calistir('is kuyruga girdi', fn() => Test::dogrula(count($kuyruk->isler) === 1, 'kuyruk bos'));
Test::calistir('is yanitta calismadi', fn() => Test::dogrula(sinifla('kucukresim') === 'ertelendi', 'sinif ' . sinifla('kucukresim')));
Test::ozet();
printf("kucukresim: %s\n", sinifla('kucukresim'));
yanit 201 doner              gecti
is kuyruga girdi             gecti
is yanitta calismadi         gecti
toplam: 3 gecti, 0 kaldi
kucukresim: ertelendi

Üç test geçti ve iş ertelendi: kaydedildi, ama koşmadı. Testin doğruladığı şey tam olarak budur. “İş kuyruğa girdi” cümlesi doğru ve “iş koştu” cümlesi bu testte hiç denenmedi. Sahte kuyruk işi çalıştırmadığı için özellik testi, işin ne yaptığı hakkında bir şey söyleyemez.

Karşıt: Kuyruk Boşaltılınca İş Koştu

birim.php aynı isteği yapar, sonra isle() ile kuyruğu boşaltır. Bu kez kucukresim koştu. İşin sonucu da doğrulanır: genişlik 800 iken sonuç 200 olmalıdır. Son test işi kuyruktan bağımsız, doğrudan sınar; birim testi budur.

<?php
// birim.php — karşıt: kuyruk isle() ile boşaltılınca iş koşar; birim testi işi doğrudan sınar
require_once 'uygulama.php';
require_once 'test.php';

$kuyruk = new SahteKuyruk();
(new YuklemeDenetleyicisi($kuyruk))->yukle(800);
$sonuclar = $kuyruk->isle();

Test::calistir('isle() isi calistirdi', fn() => Test::dogrula(sinifla('kucukresim') === 'koştu', 'sinif ' . sinifla('kucukresim')));
Test::calistir('kucuk resim genisligi', fn() => Test::dogrula($sonuclar === [200], 'sonuc ' . json_encode($sonuclar)));
Test::calistir('is tek basina', fn() => Test::dogrula((new KucukResimIsi(100))->calistir() === 25, 'sonuc yanlis'));
Test::ozet();
printf("kucukresim: %s\n", sinifla('kucukresim'));
isle() isi calistirdi        gecti
kucuk resim genisligi        gecti
is tek basina                gecti
toplam: 3 gecti, 0 kaldi
kucukresim: koştu

Aynı iş, aynı denetleyici, aynı istek: yalnız isle() çağrıldığında sınıf ertelendiden koştuya geçti. Yani sınıfı değiştiren isteğin kendisi değil, kuyruğun boşaltılmasıdır. Üçüncü test kuyruğa hiç dokunmaz: KucukResimIsi doğrudan kurulur, calistir() çağrılır ve sonuç 25 olarak okunur.

Sınırlayıcı Ölçüm

Sahte kuyruk, işin gövdesini ölçmez. sinir.php denetleyiciye bilerek yanlış çalışan HataliKucukResimIsi verir. Önce aynı özellik testi, sonra aynı işin birim testi koşar.

<?php
// sinir.php — sahte kuyruk işin gövdesini ölçmez: hatalı iş özellik testini geçer
require_once 'uygulama.php';
require_once 'test.php';

$kuyruk = new SahteKuyruk();
$yanit = (new YuklemeDenetleyicisi($kuyruk, HataliKucukResimIsi::class))->yukle(800);

echo "özellik testi\n";
Test::calistir('yanit 201 doner', fn() => Test::dogrula($yanit[0] === 201, "durum {$yanit[0]}"));
Test::calistir('is kuyruga girdi', fn() => Test::dogrula(count($kuyruk->isler) === 1, 'kuyruk bos'));
Test::calistir('is yanitta calismadi', fn() => Test::dogrula(sinifla('kucukresim') === 'ertelendi', 'sinif yanlis'));

echo "birim testi\n";
$sonuc = (new HataliKucukResimIsi(800))->calistir();
Test::calistir('kucuk resim genisligi', fn() => Test::dogrula($sonuc === 200, "beklenen 200, gelen $sonuc"));
Test::ozet();
özellik testi
yanit 201 doner              gecti
is kuyruga girdi             gecti
is yanitta calismadi         gecti
birim testi
kucuk resim genisligi        kaldi — beklenen 200, gelen 3200
toplam: 3 gecti, 1 kaldi

Özellik testinin üç doğrulaması hatalı işle de geçti, çünkü hiçbiri işin gövdesine bakmıyor: genişlik 800 iken iş 3200 döndürüyor, ama sahte kuyruk onu hiç çalıştırmadı. Hatayı yalnız birim testi yakaladı. Bir özellik testi yolun bağlandığını doğrular; yolun sonunda doğru işin yapıldığını doğrulamaz. İkisi birbirinin yerine geçmez.

Bu ayrımın bir bedeli de vardır. İşi gerçekten koşturan bir özellik testi, kuyruğu isle() ile boşaltmak zorunda kalır ve iş artık yanıttan sonra değil, testin içinde koşar. Böyle bir test ertelemenin kendisini ölçmez, yalnız işin sonucunu ölçer. Hangi sorunun sorulduğuna göre ikisinden biri seçilir.

Özet

  • Bu dersin ölçü sınıfı ertelendi: sahte kuyrukta kucukresim işi kaydedildi (ertele()), ama özellik testinde koşmadı.
  • Test kitaplığı olmadan iki fonksiyonlu bir doğrulayıcı (dogrula() ve calistir()) özellik ve birim testlerini yazmaya yetti.
  • Karşıt: isle() çağrılınca aynı iş koştu; sınıfı değiştiren istek değil, kuyruğun boşaltılmasıdır.
  • Sınırlayıcı ölçümde bilerek yanlış çalışan iş özellik testini geçti, birim testi 800 yerine 3200 sonucunu yakaladı.
  • Özellik testi yolun bağlandığını, birim testi işin doğru yaptığını doğrular; biri ötekinin yerini tutmaz.

Kurs Kapanışı

Bu kursta 21 derste bir parçanın bir istekte koşup koşmadığını ölçtük: çerçevenin temelleri, görünümler ve veri, uygulama hizmetleri. Her ders kendi ölçü sınıfını ve bir karşıt ölçümünü taşıdı. Aşağıdaki tablo hepsini tek yerde toplar; sınıf ve karşıt hücreleri kaynak dersin özetinden alınmıştır.

Ders Ölçülen parça Sınıf Karşıt ölçüm
Neden Çerçeve kaydet eylemi, onbellek dinleyicisi koştu Çekirdek 4 istek 12 parça: 4 koştu, 3 atlandı, 3 ertelendi, 2 bağlanmadı
İstek–Yanıt Yaşam Döngüsü yanittanSonra() ile bırakılan iş ertelendi Aynı iş eylemde doğrudan çağrılınca koştu
Yönlendirme Gölgelenen form rotası bağlanmadı Sıra değişince ya da {id:\d+} ile daraltılınca form koştu
Denetleyiciler Eylem, bağımlılık çözüldü koştu Tanımsız duzenle eylemi 404, parça bağlanmadı
Ara Katmanlar dogrulama, kaydet atlandı Oturumlu istekte dört parça da koştu
Nesne Kabı ve Cepheler Ezilen DosyaYazici kaydı bağlanmadı Tek kayıtlı kapta DosyaYazici koştu
Şablon Motoru Kaçışlama adımı koştu Ham çıktı sözdiziminde kaçışlama bağlanmadı
Form ve Doğrulama Kayıt adımı, geçersiz girdi 422 atlandı Geçerli girdide kayıt koştu, 201
Nesne ve Tablo Bağlaması Kaydetme kancası koştu İzin listesinde olmayan alan bağlanmadı
İlişkiler Tembel yüklemede ilişki sorgusu ertelendi İstekli yüklemede ilişki listeyle birlikte koştu
Göçler ve Tohumlama Kayıtlı göç, ikinci çalıştırma atlandı Yeni eklenen göç koştu
Sayfalama ve Kaynak Dönüşümü Modele eklenen kolonun yanıta ulaşması bağlanmadı Dönüşüm tanımına eklenen kolon koştu
Kimlik Doğrulama ve Yetkilendirme duzenle eylemi, kapı 403 atlandı Yazının sahibinde duzenle koştu, 200
Kuyruklar ve İşler Küçük resim işi ertelendi Eşzamanlı sürücüde iş koştu
Olaylar ve Dinleyiciler dizin dinleyicisi, ad uyuşmadı bağlanmadı Ad uyuşunca dinleyici koştu
Bildirimler Seçilen posta ve veritabanı kanalları koştu Seçilmeyen kanallar bağlanmadı
Önbellekleme hesapla, ikinci istekte isabet atlandı Geçersiz kılmadan sonra hesapla yeniden koştu
Dosya Depolama yukle() ve yaz(), iki disk koştu Adı geçmeyen uzak disk bağlanmadı, 500
Zamanlanmış Görevler rapor görevi ertelendi Saat 60’ta koştu, 30/59/61’de atlandı
Yerelleştirme en/indirim metni bağlanmadı Anahtar varken istenen dil koştu
Test Sahte kuyruktaki iş ertelendi isle() çağrılınca iş koştu

Derslerin dağılımı 6 koştu, 5 ertelendi, 5 atlandı, 5 bağlanmadı olarak okunur. Bu, 21 dersin her birinin kendi ölçü sınıfını sayar. Neden Çerçeve dersindeki çekirdeğin dağılımı ise ayrı bir şeyi sayar: o dersin 12 beklenen parçasından 4 koştu, 3 ertelendi, 3 atlandı, 2 bağlanmadı; yani 4 istekte 4 parça koştu, 8 parça koşmadı.

Tablodaki dağılım, bir uygulamada beklenen çoğu parçanın bir istekte koşmadığını gösterir; yanıt yine de 200 ya da 201 olabilir. Hangi sınıfın hangi yoldan geldiğini bilmek, bir test yazarken neyi doğruladığını bilmeye yarar. Kursun bıraktığı soru, bu uygulamanın kalitesinin ölçülmesidir: kodun biçimi, tekrarı ve hata sınıfları gibi başka bir konu alanı.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat