← Назад к вопросам

Пишешь ли тесты на проекте?

1.3 Junior🔥 231 комментариев
#Тестирование

Комментарии (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 без них.

Пишешь ли тесты на проекте? | PrepBro