# 12. Тестирование

[← Оглавление](./index.md)

---

## Код должен быть тестируемым — изолируйте зависимости

**ПЛОХО — ПРЯМОЙ ВЫЗОВ API, НЕЛЬЗЯ ПРОТЕСТИРОВАТЬ**
```php
class LeadService {
    public function create(array $data): int {
        return \CCrmLead::Add($data); // нельзя замокать
    }
}
```

**ХОРОШО — РЕПОЗИТОРИЙ ЧЕРЕЗ ИНТЕРФЕЙС**
```php
interface LeadRepository {
    public function save(Lead $lead): void;
}

class LeadService {
    public function __construct(private LeadRepository $repo) {}
    
    public function create(LeadDto $dto): Lead {
        $lead = new Lead($dto);
        $this->repo->save($lead);
        return $lead;
    }
}

// PHPUnit тест:
class LeadServiceTest extends \PHPUnit\Framework\TestCase {
    public function testCreateLead(): void {
        $mockRepo = $this->createMock(LeadRepository::class);
        $mockRepo->expects($this->once())->method('save');
        $service = new LeadService($mockRepo);
        $lead = $service->create(new LeadDto(title: 'Test Lead'));
        $this->assertSame('Test Lead', $lead->getTitle());
    }
}
```

---

## Data Providers для параметризованных тестов

**ПЛОХО — ДУБЛИРОВАНИЕ ТЕСТОВЫХ МЕТОДОВ**
```php
public function testSmallOrderDiscount(): void {
    $this->assertSame(0.0, (new DiscountCalculator())->calculate(5000.0));
}

public function testMediumOrderDiscount(): void {
    $this->assertSame(0.05, (new DiscountCalculator())->calculate(50000.0));
}
// и так далее...
```

**ХОРОШО — @DATAPROVIDER**
```php
class DiscountCalculatorTest extends \PHPUnit\Framework\TestCase {
    /** @dataProvider discountProvider */
    public function testCalculate(float $amount, float $expected): void {
        $this->assertSame($expected, (new DiscountCalculator())->calculate($amount));
    }
    
    public static function discountProvider(): array {
        return [
            'small order' => [5000.0, 0.0],
            'medium order' => [50000.0, 0.05],
            'large order' => [150000.0, 0.15],
        ];
    }
}
```