Найти в Дзене

Принцип TDD простыми словами

TDD (Test-Driven Development) - это подход к разработке, когда вы сначала пишете тест, а потом код, который делает этот тест успешным. Процесс состоит из 3 шагов, которые называют "Красный-Зеленый-Рефакторинг": Это как ускоряет разработку, так и повышает надежность кода + избавляет от необходимости писать автотесты в дальнейшем. Можно подумать, что разработка, наоборот, затягивается так как помимо написание функционала еще нужно тратить время на написание теста, но на самом деле это не так. В случае если вы пишете функционал, например, какой-то api роут. То написание идет примерно по следующей схеме: 1. Написание части функционала
2. Составление запроса в Postman
3. Запрос падает и вы сверяете трейс и ищите нужный функционал
4. Вносите исправление
5. Снова проверяете и в этот раз все хорошо
6. Пушите изменения в dev и снова составляете запрос в Postman, но уже на dev сервере
7. В случае если на dev возникла ошибка, то цикл повторяется, а в случае если ошибки нет, то он скорее всего пов
Оглавление

Что такое TDD?

TDD (Test-Driven Development) - это подход к разработке, когда вы сначала пишете тест, а потом код, который делает этот тест успешным.

Процесс состоит из 3 шагов, которые называют "Красный-Зеленый-Рефакторинг":

  1. Красный - Пишем тест, который падает (потому что кода еще нет)
  2. Зеленый - Пишем минимальный код, чтобы тест прошел
  3. Рефакторинг - Улучшаем код, сохраняя тесты зелеными

Для чего это нужно?

Это как ускоряет разработку, так и повышает надежность кода + избавляет от необходимости писать автотесты в дальнейшем.

Можно подумать, что разработка, наоборот, затягивается так как помимо написание функционала еще нужно тратить время на написание теста, но на самом деле это не так. В случае если вы пишете функционал, например, какой-то api роут. То написание идет примерно по следующей схеме:

1. Написание части функционала
2. Составление запроса в Postman
3. Запрос падает и вы сверяете трейс и ищите нужный функционал
4. Вносите исправление
5. Снова проверяете и в этот раз все хорошо
6. Пушите изменения в dev и снова составляете запрос в Postman, но уже на dev сервере
7. В случае если на dev возникла ошибка, то цикл повторяется, а в случае если ошибки нет, то он скорее всего повторится когда всплывет какой-либо баг

То есть на дистанции жизненного цикла приложения это будет отнимать только лишь больше времени. А если еще в дальнейшем будет задача покрыть текущий код тестами, то тут и времени много затратится и желания будет немного выполнять столь рутинную работу.

В случае с TDD подходом вы двигаетесь по следующему сценарию:

1. Пишете строчку теста
2. Запускаете тест и он не отрабатывает
3. Пишите строчку кода
4. Запускаете тест и в этот раз он отрабатывает

И так по кругу пока не будет написан весь функционал.

Плюсы:
- Все в рамках IDE. В левом стороне редактора можно вывести тест. В правой стороне сам функционал. Снизу консоль где запускаются тесты. Не нужно никуда переключаться, составлять запросы в Postman или аналогах и так далее
- Сразу понимаете где ошибка. Если тест вы написали строку, которая положила все тесты, то вы вносите изменения сразу в этой строке
- Вы имеете сразу покрытый тестами код и при необходимости внесения дальнейший доработок или его рефакторинга, вы уже будете уверены, что ничего не сломали
- Тесты можно повесить на CI/CD тем самым делегируя проверку функциональности на сервер, а не на ваши мануальные проверки
- Любой разработчик не смотря в код уже понимает все кейсы его использования. Например, когда упадет 404, когда 403, когда 429 и так далее, так как все это покрыто тестами.

Практический пример: Создание пользователя через REST API (Laravel)

Шаг 1: Пишем падающий тест

Сначала создадим тест для эндпоинта создания пользователя:

class UserCreationTest extends TestCase
{
use RefreshDatabase;

/** @test */
public function it_creates_a_new_user()
{
// Подготовка данных
$userData = [
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'password123'
];

// Действие - отправляем POST запрос
$response = $this->postJson('/api/users', $userData);

// Проверки
$response->assertStatus(201) // Должен вернуть статус 201 Created
->assertJsonStructure([
'data' => [
'id',
'name',
'email',
'created_at'
]
]);

// Проверяем, что пользователь действительно создался в БД
$this->assertDatabaseHas('users', [
'email' => 'john@example.com'
]);
}
}

Запускаем тест:

php artisan test tests/Feature/UserCreationTest.php

Тест падает - потому что эндпоинта /api/users еще не существует!

Шаг 2: Пишем минимальный код для прохождения теста

Создаем маршрут и контроллер:

1. routes/api.php:

Route::post('/users', [UserController::class, 'store']);

2. app/Http/Controllers/UserController.php:

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class UserController extends Controller
{
public function store(Request $request): JsonResponse
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8'
]);

$user = User::create($validated);

return response()->json([
'data' => [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'created_at' => $user->created_at
]
], 201);
}
}

Запускаем тест снова:

php artisan test tests/Feature/UserCreationTest.php

Тест проходит!

Шаг 3: Рефакторинг (улучшаем код)

Теперь можно улучшить код, не боясь сломать функциональность:

// Улучшенный контроллер
class UserController extends Controller
{
public function store(CreateUserRequest $request): JsonResponse
{
$user = User::create($request->validated());

return response()->json([
'data' => new UserResource($user)
], 201);
}
}

// Создаем Form Request для валидации
// app/Http/Requests/CreateUserRequest.php
class CreateUserRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8'
];
}
}

// Создаем Resource для преобразования данных
// app/Http/Resources/UserResource.php
class UserResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at
];
}
}

Запускаем тест еще раз - все еще зеленый!

Дополняем тестами крайние случаи

Теперь добавляем тесты для обработки ошибок:

/** @test */
public function it_returns_error_when_email_already_exists()
{
// Сначала создаем пользователя
User::factory()->create(['email' => 'existing@example.com']);

$userData = [
'name' => 'John Doe',
'email' => 'existing@example.com', // Дублирующий email
'password' => 'password123'
];

$response = $this->postJson('/api/users', $userData);

$response->assertStatus(422) // Unprocessable Entity
->assertJsonValidationErrors(['email']);
}

/** @test */
public function it_returns_error_when_required_fields_are_missing()
{
$userData = [
'name' => 'John Doe'
// Пропускаем email и password
];

$response = $this->postJson('/api/users', $userData);

$response->assertStatus(422)
->assertJsonValidationErrors(['email', 'password']);
}