Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Пишешь ли тесты на проекте?
Да, это core part моей разработки. Я убеждённый сторонник TDD (Test-Driven Development) и практикую его в большинстве проектов. Расскажу подробнее.
Философия
Тесты — это не добавление после кодирования. Это:
- Документация — что именно должна делать функция
- Safety net — перед рефакторингом и улучшениями
- Дизайн инструмент — помогает найти хорошую архитектуру
- Живой контракт — между разными частями системы
Идеальное распределение:
- Unit тесты: 70% (fast, isolated)
- Integration тесты: 20% (databases, APIs)
- E2E тесты: 10% (full workflows)
Практика на моих проектах
Проект 1: Laravel Application
// ❌ ДО (без тестов, код хрупкий)
public function createOrder($userId, $items) {
$order = Order::create([
'user_id' => $userId,
'total' => 0
]);
foreach ($items as $item) {
// Много логики здесь
$price = Product::find($item['id'])->price; // может быть NULL
$order->total += $price * $item['qty'];
}
$order->save();
return $order;
}
// ✅ ПОСЛЕ (с тестами, покрыто все edge cases)
// Unit тест
class OrderCreationTest extends TestCase {
/** @test */
public function it_creates_order_with_correct_total() {
$user = User::factory()->create();
$product = Product::factory()->create(['price' => 100]);
$order = OrderService::createOrder($user->id, [
['id' => $product->id, 'qty' => 2]
]);
$this->assertEquals(200, $order->total);
$this->assertDatabaseHas('orders', [
'id' => $order->id,
'total' => 200
]);
}
/** @test */
public function it_throws_on_invalid_product() {
$user = User::factory()->create();
$this->expectException(ProductNotFoundException::class);
OrderService::createOrder($user->id, [
['id' => 'non-existent', 'qty' => 1]
]);
}
/** @test */
public function it_applies_discounts_correctly() {
$user = User::factory()->create();
$product = Product::factory()->create(['price' => 1000]);
$order = OrderService::createOrder($user->id, [
['id' => $product->id, 'qty' => 10]
], discountCode: 'SUMMER20');
// 10 * 1000 * 0.8 = 8000
$this->assertEquals(8000, $order->total);
}
}
Проект 2: Symfony API с интеграционными тестами
class PaymentIntegrationTest extends WebTestCase {
/** @test */
public function it_processes_payment_via_stripe() {
$client = static::createClient();
// Используем VCR для запись HTTP ответов
$cassette = 'cassettes/stripe_successful_payment';
VCR::use($cassette, function () use ($client) {
$client->request('POST', '/api/v1/payments', [], [], [], [
'amount' => 9999,
'currency' => 'USD',
'token' => 'tok_visa'
]);
$this->assertResponseStatusCodeSame(201);
$response = json_decode($client->getResponse()->getContent(), true);
$this->assertEquals('succeeded', $response['status']);
});
}
}
Проект 3: Frontend тесты (React + Vitest)
describe('OrderForm', () => {
test('it submits order when form is valid', async () => {
const mockSubmit = vi.fn();
const { getByRole, getByLabelText } = render(
<OrderForm onSubmit={mockSubmit} />
);
const emailInput = getByLabelText(/email/i);
const submitButton = getByRole('button', { name: /submit/i });
await user.type(emailInput, 'test@example.com');
await user.click(submitButton);
expect(mockSubmit).toHaveBeenCalledWith({
email: 'test@example.com'
});
});
test('it shows validation errors', async () => {
const { getByRole, getByText } = render(<OrderForm />);
await user.click(getByRole('button', { name: /submit/i }));
expect(getByText(/email is required/i)).toBeInTheDocument();
});
});
Метрики покрытия
Идеальное состояние:
- Line Coverage: 85-90%
- Branch Coverage: 80%+
- Function Coverage: 90%+
# Проверяю coverage после каждого спринта
npm run test:coverage
# Результат:
# ✓ Lines: 88%
# ✓ Branches: 82%
# ✓ Functions: 91%
Инструменты, которые я использую
PHP:
- PHPUnit — основной test runner
- Mockery — для моков
- Pest — если нужен more fluent syntax
- VCR.php — запись HTTP interactions
// Пример с Mockery
test('user notification is sent', function () {
$mock = Mockery::mock(EmailService::class);
$mock->shouldReceive('send')
->with(Mockery::pattern('/test@example.com/'))
->once();
$service = new OrderService($mock);
$service->createOrder($order);
});
JavaScript/TypeScript:
- Vitest — fast, compatible with Jest syntax
- Testing Library — focus on user behavior
- Playwright — E2E testing
CI/CD интеграция:
# GitHub Actions example
- name: Run tests
run: npm run test -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
TDD процесс, который я следую
1. RED — написать падающий тест
public function test_it_calculates_shipping_cost() {
$calculator = new ShippingCalculator();
$cost = $calculator->calculate(weight: 5, distance: 100);
$this->assertEquals(50, $cost);
}
2. GREEN — минимальный код для прохода
class ShippingCalculator {
public function calculate(int $weight, int $distance): int {
return 50; // ✓ Тест проходит!
}
}
3. REFACTOR — улучшить код
class ShippingCalculator {
const BASE_RATE = 10; // per kg
const DISTANCE_RATE = 0.1; // per km
public function calculate(int $weight, int $distance): int {
return (int) ($weight * self::BASE_RATE + $distance * self::DISTANCE_RATE);
}
}
Когда NOT писать тесты
❌ Не пишу для:
- Configuration files
- View templates (если нет логики)
- Migrations
- One-off scripts
✅ Пишу для всего остального
Лучшие практики
// ✅ Хорошо — descriptive names
test('it_prevents_duplicate_orders_with_same_idempotency_key');
// ❌ Плохо — неясно что тестируем
test('test_order');
// ✅ Хорошо — один assert за тест
test('order_total_calculated_correctly', function () {
$order = Order::factory()->create();
$this->assertEquals(100, $order->calculateTotal());
});
// ❌ Плохо — множество asserts
test('order_works', function () {
$order = Order::factory()->create();
$this->assertTrue($order->exists);
$this->assertEquals(100, $order->total);
$this->assertNotNull($order->created_at);
});
Результат
На проектах с high test coverage:
- Рефакторинг без страха
- Быстрое обнаружение регрессий
- Лучшая коммуникация через тесты
- Меньше багов в production
- Проще онбордить новых разработчиков
Тесты — это инвестиция в качество и скорость разработки. Я не начинаю feature без них.