PHP
Meydan okuma - Test yazma
PHPUnit, mock ve AAA düzeniyle bir sipariş servisi için test paketi yazarsın.
Test düzeni: AAA
Bu derste test yazmayı öğrenirsin. AAA düzeni her testi üç adıma böler: Arrange (düzenle) test öncesi hazırlığı yapar — nesneleri ve veriyi kurar; Act (çalıştır) test edilen davranışı çağırır; Assert (doğrula) sonucun beklenen değere eşit olduğunu denetler.
Başlangıç kodunda sade bir TestCase sınıfı verilir: assertTrue ve assertEqual yöntemleri $passed ve $failed sayaçlarını tutar. OrderService test edilecek hizmet, MockLogger ise bağımlılığın yedeğidir. Sen OrderServiceTest extends TestCase yazıp testPlaceOrder ve testCount yöntemlerini AAA düzeninde eklersin.
Test double ve setup
Test double gerçek bir bağımlılığın yerine geçen nesnedir. Bu projede MockLogger bir test double'dır: log() çağrılarını bir diziye kaydeder; gerçek bir dosyaya veya veritabanına yazmaz. Böylece test hızlı, tekrarlanabilir ve yan etkisiz olur.
AAA'nin Arrange adımında test edilen nesneyi ve double'ı birlikte kurarsın. testCount içinde OrderService ve MockLogger örnekleri oluşturursun; Act adımında sipariş ekler ve log çağrısı yaparsın; Assert adımında $svc->count() ve count($logger->calls) beklenen değerle karşılaştırılır. Her test kendi düzenlemesini yapar; testler birbirinden bağımsızdır.
Uygulama OrderServiceTest sınıfını yaz
Başlangıç kodunda TestCase, OrderService ve MockLogger hazır. Sen OrderServiceTest extends TestCase sınıfını yazacaksın.
Üç yöntem ekle: run(): void önce testPlaceOrder'ı sonra testCount'u çağırır. testPlaceOrder(): void AAA düzeninde çalışır — Arrange: new OrderService(); Act: placeOrder("Kale", 3); Assert: $this->assertEqual($order["total"], 30, "toplam hesap"). testCount(): void yine AAA'dır — Arrange: OrderService ve MockLogger; Act: placeOrder("Defter", 1) ve $logger->log("sipariş eklendi"); Assert: $svc->count() 1'e ve count($logger->calls) 1'e eşit. Demoyu çalıştır: $test = new OrderServiceTest(); $test->run(); ve sonuçları yazdır.
Örnek çözümü göster
void { $this->testPlaceOrder(); $this->testCount(); } public function testPlaceOrder(): void { // Arrange $svc = new OrderService(); // Act $order = $svc->placeOrder("Kale", 3); // Assert $this->assertEqual($order["total"], 30, "toplam hesap"); } public function testCount(): void { // Arrange $svc = new OrderService(); $logger = new MockLogger(); // Act $svc->placeOrder("Defter", 1); $logger->log("sipariş eklendi"); // Assert $this->assertEqual($svc->count(), 1, "sayı arttı"); $this->assertEqual(count($logger->calls), 1, "log çağrıldı"); } } yaz. Sonra demoyu çalıştır.
Örnek çözüm:
<?php
// Tüm parçalar + test katmanı: OrderServiceTest yaz
class ProductRepository {
private PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
$this->pdo->exec("CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price INTEGER)");
$stmt = $this->pdo->prepare("INSERT INTO products (name, price) VALUES (?, ?)");
$stmt->execute(["Kale", 5]);
$stmt->execute(["Defter", 15]);
}
public function all(): array {
$rows = $this->pdo->query("SELECT id, name, price FROM products ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
return array_map(fn($r) => [
"id" => (int)$r["id"],
"name" => $r["name"],
"price" => (int)$r["price"]
], $rows);
}
}
function handleGet(PDO $pdo): string {
$repo = new ProductRepository($pdo);
return json_encode(["products" => $repo->all()], JSON_UNESCAPED_UNICODE);
}
class AuthService {
private array $users = [];
private string $csrf = "";
public function register(string $user, string $pass): void {
$this->users[$user] = password_hash($pass, PASSWORD_DEFAULT);
}
public function login(string $user, string $pass): bool {
if (!isset($this->users[$user])) return false;
return password_verify($pass, $this->users[$user]);
}
public function csrfToken(): string {
if ($this->csrf === "") {
$this->csrf = bin2hex(random_bytes(8));
}
return $this->csrf;
}
public function applyHeaders(): void {
header("X-Frame-Options: DENY");
header("X-Content-Type-Options: nosniff");
}
}
interface NotificationChannel {
public function send(string $message): string;
}
class EmailChannel implements NotificationChannel {
public function send(string $message): string {
return "E-posta: " . $message;
}
}
class SmsChannel implements NotificationChannel {
public function send(string $message): string {
return "SMS: " . $message;
}
}
interface NotificationObserver {
public function onSent(string $log): void;
}
class LogObserver implements NotificationObserver {
public array $logs = [];
public function onSent(string $log): void {
$this->logs[] = $log;
}
}
class NotificationFactory {
public static function make(string $type): NotificationChannel {
return match ($type) {
"email" => new EmailChannel(),
"sms" => new SmsChannel(),
default => throw new InvalidArgumentException("Bilinmeyen kanal"),
};
}
}
class NotificationService {
private array $observers = [];
public function attach(NotificationObserver $o): void {
$this->observers[] = $o;
}
public function notify(string $type, string $message): string {
$channel = NotificationFactory::make($type);
$result = $channel->send($message);
foreach ($this->observers as $o) {
$o->onSent($result);
}
return $result;
}
}
class TestCase {
public int $passed = 0;
public int $failed = 0;
public function assertTrue(bool $cond, string $msg = ""): void {
if ($cond) { $this->passed++; } else { $this->failed++; echo "FAIL: $msg\n"; }
}
public function assertEqual($a, $b, string $msg = ""): void {
$this->assertTrue($a === $b, $msg);
}
}
class OrderService {
private array $orders = [];
public function placeOrder(string $product, int $qty): array {
$order = ["product" => $product, "qty" => $qty, "total" => $qty * 10];
$this->orders[] = $order;
return $order;
}
public function count(): int {
return count($this->orders);
}
}
class MockLogger {
public array $calls = [];
public function log(string $msg): void {
$this->calls[] = $msg;
}
}
class OrderServiceTest extends TestCase {
public function run(): void {
$this->testPlaceOrder();
$this->testCount();
}
public function testPlaceOrder(): void {
// Arrange
$svc = new OrderService();
// Act
$order = $svc->placeOrder("Kale", 3);
// Assert
$this->assertEqual($order["total"], 30, "toplam hesap");
}
public function testCount(): void {
// Arrange
$svc = new OrderService();
$logger = new MockLogger();
// Act
$svc->placeOrder("Defter", 1);
$logger->log("sipariş eklendi");
// Assert
$this->assertEqual($svc->count(), 1, "sayı arttı");
$this->assertEqual(count($logger->calls), 1, "log çağrıldı");
}
}
// Demo:
$test = new OrderServiceTest();
$test->run();
echo "Geçen: " . $test->passed . "\n";
echo "Kalan: " . $test->failed . "\n";
Kendini test et
-
Test kavramlarını eşleştir
- assertEqual
- İki değerin eşitliğini denetler
- Mock
- Gerçek bağımlılığın yedeği
- Arrange
- Test öncesi hazırlık adımı
- assertion
- Beklenti ifadesi
-
Mock (test double) neden kullanılır?
- Kodu daha yavaş çalıştırmak için
- Gerçek bağımlılığı taklit ederek testi izole etmek için
- Veritabanına gerçek veri yazmak için
- Hata sayısını artırmak için
Cevabı göster
Gerçek bağımlılığı taklit ederek testi izole etmek için — Doğru. Mock, gerçek bağımlılığın davranışını taklit eder; böylece test edilen sınıf izole çalışır.
-
Assertion çağrısını tamamla
$this->____($order["total"], 30, "toplam hesap");
Cevabı göster
$this->assertEqual($order["total"], 30, "toplam hesap");
Sıradaki ders: Meydan okuma - Mini framework