PHPUnit ile Birim Test Yazmak: Kurulum, Sahte Nesneler ve Kapsam

PHPUnit ile Birim Test Yazmak: Kurulum, Sahte Nesneler ve Kapsam

5 dakika okuma

Test yazmanın karşılığı, hata sayısının azalmasından çok değişiklik hızının artmasıdır. Testi olmayan bir kod tabanında her düzeltme, başka bir yeri bozma riski taşır ve bu risk zamanla değişiklikten kaçınmaya dönüşür. Testler bu riski ölçülebilir hale getirir.

Bu bölümde PHPUnit kurulumu, ilk testin yazılması, veri sağlayıcılar, sahte nesneler ve test edilebilir kodun özellikleri ele alınıyor.

Kurulum ve yapılandırma

Terminal2 satır
composer require --dev phpunit/phpunit
./vendor/bin/phpunit --version

Yapılandırma dosyası, testlerin nerede aranacağını ve hangi kaynak dizininin kapsama dahil olduğunu belirtir. Katı ayarlar, testlerde gözden kaçan uyarı ve kullanımdan kaldırma bildirimlerinin fark edilmesini sağlar.

XML16 satır
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
         failOnWarning="true"
         failOnDeprecation="true"
         colors="true">
    <testsuites>
        <testsuite name="birim">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
    <source>
        <include>
            <directory>src</directory>
        </include>
    </source>
</phpunit>

İlk test

Test sınıfları test edilen sınıfın yapısını izler ve ayrı bir ad alanında durur. Her test tek bir davranışı doğrular; adı, doğrulanan davranışı anlatır.

PHP29 satır
namespace Uygulama\Test\Siparis;

use PHPUnit\Framework\TestCase;
use Uygulama\Siparis\Sepet;

final class SepetTest extends TestCase
{
    public function testBosSepetinToplamiSifirdir(): void
    {
        $sepet = new Sepet();

        self::assertSame(0, $sepet->toplamKurus());
    }

    public function testEklenenUrunlerToplama_yansir(): void
    {
        $sepet = new Sepet();
        $sepet->ekle(new Urun('Kalem', 1500), adet: 2);

        self::assertSame(3000, $sepet->toplamKurus());
    }

    public function testStoktanFazlaEklemeReddedilir(): void
    {
        $this->expectException(DomainException::class);

        (new Sepet())->ekle(new Urun('Kalem', 1500, stok: 1), adet: 5);
    }
}

Test gövdesi üç bölümden oluşur: hazırlık, çalıştırma ve doğrulama. Bu ayrımın korunması, testin ne yaptığını okumayı kolaylaştırır. Tek bir testte birden fazla senaryo doğrulanmaz; başarısızlık durumunda hangi davranışın bozulduğu belirsizleşir.

Karşılaştırmalarda tip denetimi yapan doğrulama tercih edilir. Gevşek karşılaştırma kullanan doğrulamalar, metin olarak dönen bir sayıyı da başarılı sayar ve gerçek bir kusuru gizler.

Veri sağlayıcılar

Aynı davranışın farklı girdilerle sınanması gerektiğinde test gövdesi kopyalanmaz. Veri sağlayıcı, girdi ve beklenen çıktı çiftlerini üretir; her çift ayrı bir test olarak çalışır ve başarısız olan durum adıyla raporlanır.

PHP17 satır
use PHPUnit\Framework\Attributes\DataProvider;

final class SlugTest extends TestCase
{
    #[DataProvider('ornekler')]
    public function testBasliktanSlugUretir(string $girdi, string $beklenen): void
    {
        self::assertSame($beklenen, Slug::uret($girdi));
    }

    public static function ornekler(): iterable
    {
        yield 'türkçe harfler' => ['Kaz Dağları', 'kaz-daglari'];
        yield 'noktalama' => ['PHP 8.4: Yenilikler', 'php-8-4-yenilikler'];
        yield 'baştaki boşluk' => ['  Deneme  ', 'deneme'];
    }
}

Sahte nesneler

Birim testin dış dünyaya çıkmaması gerekir. Veritabanı, dosya sistemi ve ağ çağrıları testleri yavaşlatır, ortama bağımlı hale getirir ve başarısızlıkların nedenini belirsizleştirir. Bu bağımlılıklar arayüz üzerinden verildiğinde yerlerine sahte nesneler konabilir.

PHP16 satır
final class SiparisServisiTest extends TestCase
{
    public function testOdemeBasarisizsaSiparisOlusturulmaz(): void
    {
        $odeme = $this->createMock(OdemeSaglayici::class);
        $odeme->method('tahsilEt')->willThrowException(new YetersizBakiye(5000));

        $depo = $this->createMock(SiparisDeposu::class);
        $depo->expects(self::never())->method('kaydet');    // hiç çağrılmamalı

        $servis = new SiparisServisi($odeme, $depo);

        $this->expectException(YetersizBakiye::class);
        $servis->olustur($this->ornekSepet());
    }
}

Sahte nesnelerin iki farklı kullanımı vardır. Birincisi yalnızca hazır değer döndürmek, ikincisi çağrının yapılıp yapılmadığını doğrulamaktır. İkinci kullanımda test, sınıfın iç işleyişine bağlanır; bu yüzden yalnızca çağrının kendisi bir dış etki üretiyorsa tercih edilmelidir.

Sahte nesnelerin aşırı kullanımı testleri kırılganlaştırır. Bir testte üç dörtten fazla sahte nesne gerekiyorsa, sınıfın çok fazla iş yaptığı ve bölünmesi gerektiği düşünülmelidir.

Test edilebilir kod

Bazı kodlar doğaları gereği test edilemez. Ortak özellikleri, bağımlılıklarını kendi içinde üretmeleri ve dış dünyaya doğrudan erişmeleridir.

  • Sınıf içinde new ile üretilen bağımlılıklar değiştirilemez; yapıcı metottan alınmalıdır.
  • Statik çağrılar yer değiştirilemez; nesne üzerinden çağrı tercih edilir.
  • O anki zaman doğrudan üretiliyorsa zamana bağlı kurallar sınanamaz; saat bir arayüzden alınır.
  • Rastgele değerler dışarıdan verilebilir olmalıdır.
  • Çıktının doğrudan ekrana basılması yerine döndürülmesi, doğrulamayı kolaylaştırır.

Test türleri ve dengesi

Birim testler tek bir sınıfı yalıtılmış biçimde sınar; hızlı çalışır ve başarısız olduğunda sorunun yeri bellidir. Bütünleşme testleri birkaç parçanın birlikte çalışmasını, uçtan uca testler ise kullanıcı akışının tamamını doğrular. Üçü birbirinin yerine geçmez.

Dengeli bir düzende birim testler çoğunluğu oluşturur, bütünleşme testleri sınırları ve dış bağımlılıkları kapsar, uçtan uca testler ise yalnızca kritik akışlar için yazılır. Yavaş çalışan testlerin sayısı arttıkça, geliştiricinin testleri çalıştırma sıklığı düşer ve testlerin sağladığı fayda azalır.

Veritabanına dokunan testler ayrı bir takım altında toplanmalı ve gerçek veritabanına karşı çalıştırılmalıdır. Sahte bir veritabanı katmanına karşı yazılan testler, asıl sorunların çıktığı sorgu düzeyini hiç sınamaz.

Terminal2 satır
./vendor/bin/phpunit --testsuite birim         # her değişiklikte
./vendor/bin/phpunit --testsuite butunlesme    # dağıtım öncesi

Kapsam ölçümü

Kapsam oranı, çalıştırılan satırların yüzdesini gösterir; doğrulanan davranışların oranını değil. Hiçbir doğrulama içermeyen bir test bile kapsamı yükseltir. Bu yüzden oran bir hedef değil, test edilmemiş bölgeleri bulmaya yarayan bir araç olarak kullanılır.

Terminal2 satır
./vendor/bin/phpunit --coverage-text
./vendor/bin/phpunit --coverage-html rapor/kapsam

Öncelik, iş kurallarının bulunduğu sınıflara verilir. Alıcı ve atayıcı metotların, yapılandırma dosyalarının ve çatı kodunun test edilmesi çoğu durumda emeğe değmez. Bir hata üretimde ortaya çıktığında ise önce o hatayı yeniden üreten bir test yazılır, sonra düzeltme yapılır; böylece aynı hatanın geri dönmediği garanti altına alınır.

Kontrol listesi

  • Testler ayrı dizinde ve kendi ad alanında.
  • Her test tek bir davranışı doğruluyor, adı davranışı anlatıyor.
  • Karşılaştırmalarda tip denetimli doğrulama kullanılıyor.
  • Tekrarlanan senaryolar veri sağlayıcıyla yazılıyor.
  • Birim testler ağ, dosya ve veritabanına erişmiyor.
  • Bağımlılıklar yapıcı metottan alınıyor.
  • Üretimde çıkan her hata için önce testi yazılıyor.

İlgili yazılar

PHP eğitim serisi

Bu yazı, temel sözdiziminden üretim ortamına kadar ilerleyen 25 bölümlük serinin bir parçası. Bölümler sırayla okunacak biçimde hazırlandı, ancak her biri tek başına da kullanılabilir.

Temeller

  1. Kurulum ve temel sözdizimi
  2. Döngüler ve akış kontrolü
  3. Diziler
  4. Fonksiyonlar
  5. String fonksiyonları
  6. Süper globaller
  7. Form işleme
  8. Dosya işlemleri

Dilin ayrıntıları

  1. Tip sistemi ve strict_types
  2. Hata ve istisna yönetimi
  3. Düzenli ifadeler
  4. Tarih ve saat işlemleri

Nesne yönelimli PHP

  1. Sınıf ve nesne
  2. Kalıtım, arayüz ve trait
  3. Statik üyeler, sabitler ve enum
  4. Namespace, Composer ve autoload

Veri ve güvenlik

  1. PDO ile veritabanı işlemleri
  2. Oturum yönetimi ve kimlik doğrulama
  3. Güvenli çıktı ve XSS
  4. JSON ile veri değişimi

Dış dünya ve üretim

  1. cURL ile dış API çağırma
  2. Kendi REST API'ni yazmak
  3. PHPUnit ile birim test (bu yazı)
  4. Hata ayıklama ve performans ölçümü
  5. Üretime alma ve güvenlik

Yorumlar

Bu form reCAPTCHA ile korunur; Google Gizlilik Politikası ve Hizmet Şartları geçerlidir.

İlk yorumu sen yap.

İlgili Yazılar