Yazılım geliştirmede karşılaştığımız zorlu hatalardan biri, testlerden geçmesine rağmen kullanıcıda çökmesine neden olan hatalardır. Bugün laravel-config-sso admin arayüzünde bir hata ile karşılaştım ve gerçekte çözüm, bir ikonu değiştirip düzeltmek değildi — hatayı ilk başta görebilen bir test yazmaktı.
<h2>
<a name="the-bug-wrong-icon-name-crashes-only-at-runtime" href="#the-bug-wrong-icon-name-crashes-only-at-runtime">
</a>
Hata: Yanlış ikon adı, yalnızca çalışma zamanında çökme
</h2>
<p>Admin arayüzü <a href="https://fluxui.dev" target="_blank" rel="noopener noreferrer">Flux</a> kullanıyor. Flux, ikonları <code><delegate-component/></code> üzerinden çözümler ve var olmayan bir ad için <strong>hata fırlatır</strong>:<br/></p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>Flux component [icon.ellipsis] does not exist.</code></pre>
</div>
<p>Bu basit bir hata. Flux, <strong>Heroicons</strong> ile gelir, Lucide ile değil. Böylece Lucide alışkanlıklarınız sizi yanıltabilir:</p>
<div class="table-wrapper-paragraph">
<table>
<thead>
<tr>
<th>Yazdığınız (Lucide)</th>
<th>Flux'un istediği (Heroicon)</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ellipsis</code></td>
<td><code>ellipsis-horizontal</code></td>
</tr>
<tr>
<td><code>trash-2</code></td>
<td><code>trash</code></td>
</tr>
<tr>
<td><code>eye-off</code></td>
<td><code>eye-slash</code></td>
</tr>
</tbody>
</table>
</div>
<h2>
<a name="why-feature-tests-dont-catch-it" href="#why-feature-tests-dont-catch-it">
</a>
Özellik testleri neden bunu yakalayamaz?
</h2>
<p>İlginç bir kısım. Admin rotasını vuran bir özellik testim vardı ve 200 döndürüyor. Test yeşil. Ama gerçek UI çöküyor. Neden?</p>
<p>Çünkü başsız test ortamında <strong>Flux ikonları no-op olarak render edilir.</strong> Gerçek bir <code><delegate-component/></code> başlatılmadığı için ikon adı hiçbir zaman çözülemez. Çöküş yalnızca tam bir başlatma altında ( <code>testbench serve</code> ) ortaya çıkıyor — tam da otomatik testlerinizin gitmediği yer.</p>
<p>Analojisi: Belgeyi yalnızca <em>yazdırdığınızda</em> çalışan bir yazım denetleyici ile aynı. Testleriniz mutlu bir şekilde yazmaya devam ederken, çöküş yazıcıda bekliyor.</p>
<h2>
<a name="the-fix-a-static-test-that-reads-the-blade-and-validates-every-icon" href="#the-fix-a-static-test-that-reads-the-blade-and-validates-every-icon">
</a>
Çözüm: Blade'i okuyan ve her ikonu doğrulayan bir statik test
</h2>
<p>Çalışma zamanına güvenmek yerine, Blade görünümünü okuyan, her ikon adını çıkaran ve Flux'un gerçekten her biri için bir stub gönderdiğini doğrulayan bir Pest testi yazdım:<br/></p>
<div class="highlight js-code-highlight">
<pre class="highlight php"><code>$fluxIconStubs = base_path('vendor/livewire/flux/stubs/resources/views/flux/icon');it(‘only references Flux icons that exist’, function () use ($fluxIconStubs) {
expect(is_dir($fluxIconStubs))->toBeTrue(“Flux icon stubs not found”);
$view = file_get_contents(__DIR__.'/../../resources/views/livewire/sso-providers.blade.php');
// Statik `icon="name"` plus quoted tokens inside dynamic
// `icon="{{ $cond ? 'eye-slash' : 'eye' }}"` expressions
preg_match_all('/icon="([a-z][a-z0-9-]*)"/', $view, $static);
preg_match_all('/icon="\{\{(.+?)\}\}"/', $view, $dynamic);
$names = $static[1];
foreach ($dynamic[1] as $expression) {
preg_match_all('/"([a-z][a-z0-9-]*)"/', $expression, $tokens);
$names = array_merge($names, $tokens[1]);
}
$names = array_values(array_unique($names));
expect($names)->not->toBeEmpty();
foreach ($names as $name) {
expect(is_file("{$fluxIconStubs}/{$name}.blade.php"))->toBeTrue("Flux has no icon [{$name}] — use a valid Heroicon name (Flux ships Heroicons, not Lucide).");
}});
<p>Bu testte hoşlandığım şeyler:</p>
<ul>
<li><strong>Gerçek bilgi kaynağına karşı çalışıyor.</strong> Flux'un ikon kaydı, <code>vendor/</code> içinde bir stubler dizisidir. Test, doğrudan buna karşı kontrol yapıyor - bayat bir sabit listeye değil.</li>
<li><strong>Dinamik ikonları işliyor.</strong> <code>eye</code> / <code>eye-slash</code> gibi anahtarlar genellikle gözden kaçar. İkinci regex, Blade ifadeleri içindeki alıntılanmış tokenleri yakalar.</li>
<li><strong>Başarısızlık mesajı öğreticidir.</strong> Kırıldığında, size gerçek nedeni söyler: <em>"Flux, Heroicons gönderiyor, Lucide değil."</em> Gelecekteki ben bunun için minnettar olacak.</li>
</ul>
<p>Bu testin faydası hemen kendini gösterdi: aynı gün, Flux'un ücretsiz ve Pro ikonları arasında bir başka paket bir hata daha aldı (bir <code>webhook</code>/<code>ellipsis</code>/<code>list</code> seti yalnızca Flux Pro'da mevcuttur). Böyle bir guard test, "üretimde çökme" durumunu "CI'de başarısızlık" durumuna çevirir — işte istediğiniz yer.</p>
<h2>
<a name="when-to-reach-for-this-pattern" href="#when-to-reach-for-this-pattern">
</a>
Bu desene ne zaman başvurmalısınız?
</h2>
<p>Her yazım hatasının bir teste ihtiyaç duyduğuna dair bir kural yok. Bu desen, <strong>testler sırasında çalışma zamanının yalan söylediği durumlarda</strong> kendini gösterir — bir bileşen no-op olduğunda, bir adaptör çıkarıldığında, üretimden farklı bir ortamda. Burada, bir artifakt (bir Blade görünümü, bir yapılandırma dosyası, bir migration) okuyan statik bir test, dinamik bir testin yakalayamayacağı şeyleri yakalayabilir.</p>
<p>Benim kullandığım kural: bir hata yalnızca tam bir başlatma altında ortaya çıkıyorsa fakat testler başsız çalışıyorsa, CI'de tam başlatma peşinde koşmayın. Bunu kaynak dosyalar üzerinde bir statik kontrol ile daha erken yakalayın. Daha ucuz, daha hızlıdır ve asla no-op render etmez.</p>

