Проблема тестовых данных

С ростом тестовых наборов управление тестовыми данными становится одной из главных проблем поддержки. Представьте 500 тестов, каждый требующий объект User. Если модель User получает новое обязательное поле — нужно обновить все 500 тестов.

Захардкоженные данные создают три проблемы: дублирование, хрупкость и непрозрачность (тесты засорены данными, не относящимися к проверяемому).

Фабрики тестовых данных

Фабрика — централизованный модуль, создающий тестовые объекты с разумными значениями по умолчанию.

Паттерн Factory

public class UserFactory {
    private static int counter = 0;

    public static User createDefault() {
        counter++;
        return User.builder()
            .name("Test User " + counter)
            .email("user" + counter + "@test.com")
            .role("user")
            .active(true)
            .build();
    }

    public static User createAdmin() {
        return createDefault().toBuilder()
            .role("admin")
            .permissions(List.of("read", "write", "delete", "manage_users"))
            .build();
    }
}

@Test
void adminCanDeleteUsers() {
    User admin = UserFactory.createAdmin();
    User target = UserFactory.createDefault();
    assertTrue(userService.canDelete(admin, target));
}

Паттерн Builder

public class UserBuilder {
    private String name = "Default User";
    private String email = "default@test.com";
    private String role = "user";
    private boolean active = true;

    public static UserBuilder aUser() { return new UserBuilder(); }
    public UserBuilder withName(String name) { this.name = name; return this; }
    public UserBuilder withRole(String role) { this.role = role; return this; }
    public UserBuilder inactive() { this.active = false; return this; }
    public User build() { return new User(name, email, role, active); }
}

User admin = UserBuilder.aUser().withName("Alice").withRole("admin").build();

Пример на Python (Factory Boy)

import factory
from models import User

class UserFactory(factory.Factory):
    class Meta:
        model = User

    name = factory.Faker('name')
    email = factory.LazyAttribute(lambda obj: f"{obj.name.lower().replace(' ', '.')}@test.com")
    role = "user"
    active = True

class AdminFactory(UserFactory):
    role = "admin"
    permissions = ["read", "write", "delete", "manage_users"]

user = UserFactory()
admin = AdminFactory()

Тестовые фикстуры

Фикстуры управляют жизненным циклом теста — настраивают предусловия и выполняют очистку.

Фикстуры JUnit 5

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class OrderServiceTest {
    private DatabaseConnection db;

    @BeforeAll
    void setupOnce() { db = DatabaseConnection.create("test-db"); }

    @BeforeEach
    void setupEach() { db.beginTransaction(); }

    @AfterEach
    void teardownEach() { db.rollbackTransaction(); }

    @AfterAll
    void teardownOnce() { db.close(); }
}

Фикстуры Pytest

@pytest.fixture
def db():
    connection = create_test_database()
    yield connection
    connection.cleanup()

@pytest.fixture
def user(db):
    user = UserFactory.create()
    db.save(user)
    return user

def test_admin_can_delete_user(admin_user, user):
    assert user_service.can_delete(admin_user, user) is True

Фикстуры Playwright

exports.test = base.extend({
    authenticatedPage: async ({ page }, use) => {
        await page.goto('/login');
        await page.fill('#email', 'admin@test.com');
        await page.fill('#password', 'password');
        await page.click('#submit');
        await use(page);
    },
    testUser: async ({ request }, use) => {
        const response = await request.post('/api/users', {
            data: { name: 'Test User', email: 'test@example.com' }
        });
        const user = await response.json();
        await use(user);
        await request.delete(`/api/users/${user.id}`);
    }
});

Стратегии тестовых данных

СтратегияСкоростьИзоляцияСложность
Создание/удаление на тестМедленнаяВысокаяНизкая
Откат транзакцииБыстраяВысокаяСредняя
Общие справочные данныеБыстраяСредняяСредняя
Снимки БДБыстраяВысокаяВысокая

Упражнения

Упражнение 1: Библиотека фабрик

Создайте фабрики для User, Product и Order, реализуйте паттерн Builder с fluent API, создайте методы для типичных персон.

Упражнение 2: Иерархия фикстур

Реализуйте фикстуры для setup/teardown с откатом транзакций, создайте фикстуру аутентифицированного API-клиента, постройте композицию фикстур.

Упражнение 3: Сравнение стратегий

Реализуйте одни и те же 3 теста с разными стратегиями, измерьте время выполнения, дайте рекомендацию.