Проблема тестовых данных
С ростом тестовых наборов управление тестовыми данными становится одной из главных проблем поддержки. Представьте 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 теста с разными стратегиями, измерьте время выполнения, дайте рекомендацию.