Özet: Pest PHP mimari testleri, ekibinizin kodlama kurallarını otomatik testler olarak yazmanıza olanak tanır. DB facade’ını doğrudan kullanan kontroller mi? Yapı başarısız olur. Model, tenant izolasyonu trait’ini içermiyor mu? Yapı başarısız olur. Bu testler, eklediğimizden üç hafta sonra kritik bir veri izolasyonu hatasını yakaladı.
Ekibimizde tenant’lara özgü veri depolayan her modelin BelongsToTenant trait’ini kullanması gerektiğine dair bir kural vardı. Bu trait, tüm sorguları otomatik olarak mevcut tenant’a göre filtreleyen bir global scope ekler. Bunu kullanmazsanız, bir sorgu yanlışlıkla diğer tenant’lardan verileri döndürebilir.
Kural netti. Herkes bunu biliyordu. Onboarding aşamasında bahsedildi. Kod inceleme kontrol listesindeydi.
Yeni bir geliştirici ekibe katıldı. Üç hafta sonra trait olmadan yeni bir model ekledi. Kod incelemesi, değerlendirici iş mantığına odaklandığı için bunu kaçırdı; bu da oldukça iyi yazılmıştı. Model üretime geçti.
İki gün boyunca bir tenant, belirli bir raporda diğer tenant verilerinin parçalarını görebildi. Bunu destek talebi üzerinden tespit ettik, testlerimizden değil.
O gün mimari testleri eklemeye karar verdim.
Mimari Testler Nasıl Görünüyor
Mimari Testler Nasıl Görünüyor
Pest PHP, kod yapınız hakkında yargılama yapmanıza olanak tanıyan bir arch() fonksiyonu içerir. Kodunuzun ne yaptığı değil, nasıl organize edildiği hakkında.
// tests/Architecture/ArchTest.php
arch('models must use BelongsToTenant trait')
->expect('App\Models')
->toUseTrait('App\Traits\BelongsToTenant');
arch('controllers should not use DB facade')
->expect('App\Http\Controllers')
->not->toUse('Illuminate\Support\Facades\DB');
arch('services should not use Request')
->expect('App\Services')
->not->toUse('Illuminate\Http\Request');
arch('value objects must be readonly')
->expect('App\ValueObjects')
->toBeReadonly();
arch('soft delete models must also have tenant scope')
->expect('App\Models')
->toUseTrait('Illuminate\Database\Eloquent\SoftDeletes')
->toUseTrait('App\Traits\BelongsToTenant');
Bu testler, her tamamlamada CI pipeline’ımızda çalışır. Herhangi bir kural ihlal edilirse, yapı başarısız olur. Geliştirme, hangi kuralı ihlal ettiğini ve hangi dosyada sorun olduğunu açık bir hata mesajı alır.
Bizleri Kurtaran Testler
Bizleri Kurtaran Testler
Son altı ayda en fazla ihlali tespit eden mimari testler şunlardır:
Tenant izolasyon testi, BelongsToTenant trait’ini içermeyen 4 yeni modeli yakaladı. Her biri potansiyel bir veri sızıntısı olabilirdi. Bu tek test, tüm çabayı haklı çıkardı.
Kontrolcü saflığı testi, geliştiricilerin servis sınıflarını kullanmak yerine doğrudan kontrolcüler içerisinde veritabanı sorguları yazmalarını engelliyor. Bu, “hızlı bir uç noktayı ekliyorum” diyen herhangi birinin zaman zaman yapabileceği bir hata.
// Bu mimari testi başarısız kılacak bir kod
class ReportController extends Controller {
public function index() {
$data = DB::table()
->where(, auth()->user()->tenant_id)
->sum();
return response()->json();
}
}
Doğru yaklaşım, sorguyu bir servis sınıfına taşımaktır. Kontrolcü sadece servisi arar ve sonucu döndürür. Bu, kontrolcülerimizi ince tutar ve iş mantığını izole bir şekilde test edilebilir hale getirir.
Bağımlılık yönü testi, iç katmanların asla dış katmanlara bağımlı olmamasını sağlar. Servisler, Request nesnelerini içe aktaramaz. Modeller, kontrolcü sınıflarını içe aktaramaz. Bu, bağımlılık grafiğini temiz tutar ve kodun test edilmesini kolaylaştırır.
Temel Kuralların Ötesine Geçmek
Temel Kuralların Ötesine Geçmek
Temel trait ve bağımlılık kontrolleri faydalıdır, ancak asıl güç, mimari testleri özel mantıkla birleştirmekten gelir.
arch()
->expect()
->not->toUse();
arch()
->expect()
->not->toUse([]);
Env() testi, erken eklemeyi istediğim bir test. Uygulama kodunda env() çağrısı yapmak geliştirmede çalışır ama üretimde yapılandırma önbelleğe alındığında null döner. Bu, geliştiricileri en az bir kez yakalayan klasik bir Laravel tuzağıdır. Mimari testi bunu kalıcı hale getirir.
Ayrıca, hiçbir modelin $guarded özelliğinin boş bir dizi olarak ayarlanmadığından emin olan bir test ekledim. Bir geliştirici, $guarded’ı boş bir diziye ayarlayarak, bunun $fillable’ı ayarlamakla aynı olduğunu düşündüğü için bir kitle atama açığına neden olan bir olay yaşadık. Değil. Boş bir $guarded, her alanın kitle tarafından ataması yapılabilir demektir.
arch()
->expect()
->not->toUseStrictlyEmptyGuarded();
Yolda Yaptığım Bir Hata
Yolda Yaptığım Bir Hata
Mimari testleri ilk kurduğumda, fazladan abarttım. Bir çekme isteğinde 20 kural ekledim. CI pipeline, bir Noel ağacı gibi parladı. Kod tabanında 150’den fazla ihlal vardı.
Ekibin morali bozuldu. Hiç kimse, mevcut iş özellikleriyle ilgisi olmayan 150 ihlali düzeltmek istemedi. PR’ı geri almak zorunda kaldım ve farklı bir yaklaşım izlemek gerekti.
Ders açık oldu. Mimari kuralları, büyük bir kod tabanı üzerinde geriye dönük olarak bir seferde zorla uygulayamazsınız. Sosyal olarak çalışmaz, teknik olarak da.
Mevcut Bir Projeye Mimari Testleri Nasıl Tanıtırız
Mevcut Bir Projeye Mimari Testleri Nasıl Tanıtırız
Bu hatadan öğrendikten sonra bizim için işe yarayan yaklaşım şu şekildedir:
Adım 1. İlk olarak en kritik kural ile başlayın. Bizim için bu, tenant izolasyonu trait’iydi. Bir test. Birkaç ihlali düzeltin. Birleştirin.
Adım 2. Her hafta bir yeni kural ekleyin. Ekibe ayarlama yapması ve mevcut ihlalleri düzeltmesi için zaman tanıyın.
Adım 3. Birçok mevcut ihlal olan kurallar için ignoring() yöntemini kullanarak eski kodu hariç tutun.
arch()
->expect()
->not->toUse()
->ignoring();
Bu, tüm yeni kod için kuralı zorlamanıza olanak tanırken, ekibin eski kodları yeniden yapılandıracak zamanı olmasını sağlar.
Adım 4. İhlalleri zaman içinde takip edin. İyileştirme sürecinde, ignoring() istisnalarının sayısını sayan basit bir betik ekledik. O sayının her sprintte yukarı değil, aşağı gitmesi gerekiyor. Dört ayda 47’dan 12’ye düştük. Eğilim çizgisi, mutlak sayıdan daha önemlidir.
Üretimden Gerçek Rakamlar
Üretimden Gerçek Rakamlar
Mimari testleri altı ay boyunca çalıştırdıktan sonra sonuçlar kendini gösterdi.
Tenant izolasyon testi, BelongsToTenant trait’ini içermeyen 4 modeli yakaladı. Her biri tenantlar arasında veri sızıntısı olabilirdi. Bu kontrollerin algılanma süresi, “üretimde biri fark edene kadar” olmaktan “CI’da hemen” düzeyine düştü.
Kontrolcü saflığı testi, ilk ayda 23 ihlal yakaladı. Üç ay sonra bu sayı ayda 2’ye düştü. Geliştiriciler bu düzeni öğrendi ve kontrolcülerde veritabanı sorguları yazmayı bıraktı.
Env() testi, üretimde yapılandırma önbelleği etkinleştirildiğinde patlayacak olan 7 doğrudan çağrıyı yakaladı. Bunlardan biri ödeme işleme akışındaydı. O tek yakalama, belki de bir hafta sonu hata ayıklamakla kurtardı.
Farklı Ne Yapardım
Farklı Ne Yapardım
Her yeni projede mimari testleri birinci günde eklerdim. Mevcut bir kod tabanına eklemek, tüm mevcut ihlaller nedeniyle acılı oluyor. Yeniden başlamak, kuralların ilk taahhütte uygulanacağı ve asla geri dönüşümlü çalışma olmayacağı anlamına gelir.
Ayrıca, bağımlılık yönlerini test etme konusunda daha agresif olurdum. DB kontrolcü modelini erken yakaladık ama diğer bağımlılık ihlalleri için test eklemek konusunda yavaştık. Onları ekleyene kadar 40’tan fazla ihlal düzeltmek zorunda kaldık.
Ve her projenin kullandığı ortak mimari testlerden oluşan bir paylaşım paketi oluştururdum. Sonuç olarak, üç farklı projede çok benzer testler yazdık. Tekrar kullanılabilir bir test takımı zaman kazandırırdı. O paketi henüz oluşturmadım ama listemde var.
Daha Büyük Resim
Daha Büyük Resim
Mimari testler, sıkı olmanın bir amacı yok, düzenli bir kod tabanı sağlamak içindir.
Her model BelongsToTenant trait’ini taşıdığında, veri izolasyonunun her yerde sağlandığını bilirsiniz. Hiçbir kontrolcü, doğrudan veritabanına dokunmadığında, hizmet katmanının tek gerçek kaynak olduğunu bilirsiniz. Bağımlılıklar bir yönde aktığında, bir katmanı yeniden yapılandırmanın diğerini bozmayacağını bilirsiniz.
Bu garantiler zamanla birikir. Kod tabanında altı ay geçtikten sonra, testler bunun bozulmasını engeller. Yeni geliştiriciler, CI pipeline’ının buna izin vermemesi nedeniyle otomatik olarak bu kalıplara uyarak kod yazarlar.
Ekibinizin kuralları wiki’de değil, test paketinize yerleşmelidir.
Otomatik testlerinizle hangi mimari kuralları zorlamak istersiniz?
Kaynak: Orijinal Makale


