Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Фабрики данных: factory_boy + faker
factory_boy_faker

Фабрики данных: factory_boy + faker

Создаём фабрики с factory_boy для моделей БД. Генерируем реалистичные данные с faker. Зависимые фабрики для FK relationships. Переиспользование в тестах. Избавляемся от ручного создания данных

В production-тестах вы создаёте СОТНИ тестовых записей: пользователи, заказы, платежи. Ручное создание превращается в ад копипасты.

factory_boy — индустриальный стандарт для генерации тестовых данных в Python.

Цель: Научиться создавать фабрики для моделей БД, генерировать реалистичные данные и переиспользовать их во всех тестах.

Prerequisite Check

Вы должны уметь:

# Работать с PostgreSQL в pytest @pytest.fixture def db_connection(): conn = psycopg2.connect(...) yield conn def test_example(db_connection): cur = db_connection.cursor() cur.execute("INSERT INTO users ...")

Если не уверены — вернитесь к Alembic миграции.

Проблема: ручное создание тестовых данных

Пример без factory_boy (копипаста!)

# tests/test_orders.py def test_create_order(db_connection): """Тест создания заказа""" cur = db_connection.cursor() # ❌ Создаём пользователя вручную cur.execute( "INSERT INTO users (email, name) VALUES (%s, %s) RETURNING id", ("test@test.com", "Test User") ) user_id = cur.fetchone()[0] # ❌ Создаём продукт вручную cur.execute( "INSERT INTO products (title, price) VALUES (%s, %s) RETURNING id", ("Test Product", 100.0) ) product_id = cur.fetchone()[0] # ❌ Создаём заказ вручную cur.execute( "INSERT INTO orders (user_id, product_id, quantity) VALUES (%s, %s, %s) RETURNING id", (user_id, product_id, 2) ) order_id = cur.fetchone()[0] # Проверка assert order_id is not None

Проблемы:

  • ❌ 20 строк кода только для создания данных
  • ❌ Копипаста в каждом тесте
  • ❌ Хрупкий код — если добавите обязательную колонку, сломаются ВСЕ тесты
  • ❌ Нереалистичные данные — "Test User", "Test Product"

С factory_boy (2 строки!)

# С фабриками def test_create_order(db_connection): """Тест создания заказа""" order = OrderFactory(quantity=2) assert order.id is not None assert order.quantity == 2 # ✅ user и product созданы автоматически!

✅ 90% меньше кода!

Установка factory_boy

pip install factory_boy faker

factory_boy — создание фабрик faker — генерация реалистичных данных (имена, email, адреса)

Базовый пример: фабрика User

Схема БД

CREATE TABLE users ( id SERIAL PRIMARY KEY, email VARCHAR(255) NOT NULL UNIQUE, name VARCHAR(100), created_at TIMESTAMP DEFAULT NOW() );

Фабрика для User

# tests/factories.py import factory from faker import Faker fake = Faker() class UserFactory(factory.Factory): """Фабрика для создания пользователей""" class Meta: model = dict # Генерируем dict (для SQL INSERT) email = factory.LazyFunction(lambda: fake.email()) name = factory.LazyFunction(lambda: fake.name()) created_at = factory.LazyFunction(lambda: fake.date_time())

Что здесь происходит:

  • factory.Factory — базовый класс фабрики
  • model = dict — генерируем словарь (для psycopg2)
  • LazyFunction — вызывает функцию каждый раз (не один раз при импорте!)
  • fake.email() — генерирует реалистичный email: alice@example.com
  • fake.name() — генерирует имя: Alice Johnson

Использование фабрики в тесте

# tests/test_users.py from factories import UserFactory def test_create_user(db_connection): """Тест создания пользователя с фабрикой""" cur = db_connection.cursor() # Создайте пользователя через фабрику user_data = UserFactory() # Вставьте в БД cur.execute( "INSERT INTO users (email, name) VALUES (%(email)s, %(name)s) RETURNING id", user_data ) user_id = cur.fetchone()[0] # Проверка cur.execute("SELECT email FROM users WHERE id = %s", (user_id,)) result = cur.fetchone() assert result[0] == user_data["email"]

Генерация множества пользователей

def test_bulk_users(db_connection): """Создайте 10 пользователей за раз""" cur = db_connection.cursor() # Создайте 10 пользователей users = [UserFactory() for _ in range(10)] for user in users: cur.execute( "INSERT INTO users (email, name) VALUES (%(email)s, %(name)s)", user ) # Проверка cur.execute("SELECT COUNT(*) FROM users") count = cur.fetchone()[0] assert count == 10

Реалистичные данные с Faker

Доступные генераторы

from faker import Faker fake = Faker() # Личные данные fake.name() # "Alice Johnson" fake.first_name() # "Alice" fake.last_name() # "Johnson" fake.email() # "alice@example.com" fake.phone_number() # "+1-555-123-4567" # Адреса fake.address() # "123 Main St, Springfield, IL 62701" fake.city() # "Springfield" fake.country() # "United States" # Даты fake.date() # "2025-01-15" fake.date_time() # datetime(2025, 1, 15, 14, 30) fake.past_date() # Дата в прошлом fake.future_date() # Дата в будущем # Текст fake.text(max_nb_chars=200) # Случайный текст до 200 символов fake.sentence() # "This is a random sentence." # Числа fake.random_int(min=1, max=100) # Случайное число 1-100 fake.pydecimal(left_digits=5, right_digits=2) # Decimal для цен # URL fake.url() # "https://example.com" fake.slug() # "random-slug-12345"

Пример: фабрика Product

# tests/factories.py class ProductFactory(factory.Factory): """Фабрика для продуктов""" class Meta: model = dict title = factory.LazyFunction(lambda: fake.sentence(nb_words=3)) description = factory.LazyFunction(lambda: fake.text(max_nb_chars=200)) price = factory.LazyFunction(lambda: fake.pydecimal(left_digits=3, right_digits=2, positive=True)) stock = factory.LazyFunction(lambda: fake.random_int(min=0, max=100)) created_at = factory.LazyFunction(lambda: fake.past_date())

Использование:

product = ProductFactory() # { # "title": "Random product title here", # "description": "Lorem ipsum dolor sit amet...", # "price": Decimal('123.45'), # "stock": 42, # "created_at": datetime(2024, 5, 10) # }

Зависимые фабрики (Foreign Keys)

Схема БД с FK

CREATE TABLE users ( id SERIAL PRIMARY KEY, email VARCHAR(255) ); CREATE TABLE orders ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id), product_id INTEGER REFERENCES products(id), quantity INTEGER NOT NULL );

Фабрика с FK (SubFactory)

# tests/factories.py class OrderFactory(factory.Factory): """Фабрика для заказов""" class Meta: model = dict # ✅ user_id создаётся через UserFactory! user = factory.SubFactory(UserFactory) # ✅ product_id создаётся через ProductFactory! product = factory.SubFactory(ProductFactory) quantity = factory.LazyFunction(lambda: fake.random_int(min=1, max=10))

Использование в тесте:

def test_create_order_with_subfactories(db_connection): """Тест создания заказа с автоматическими зависимостями""" cur = db_connection.cursor() # Создайте заказ через фабрику order_data = OrderFactory() # order_data содержит: # { # "user": {"email": "alice@...", "name": "Alice"}, # "product": {"title": "...", "price": 100.0}, # "quantity": 2 # } # Вставьте user cur.execute( "INSERT INTO users (email, name) VALUES (%(email)s, %(name)s) RETURNING id", order_data["user"] ) user_id = cur.fetchone()[0] # Вставьте product cur.execute( "INSERT INTO products (title, price) VALUES (%(title)s, %(price)s) RETURNING id", order_data["product"] ) product_id = cur.fetchone()[0] # Вставьте order cur.execute( "INSERT INTO orders (user_id, product_id, quantity) VALUES (%s, %s, %s) RETURNING id", (user_id, product_id, order_data["quantity"]) ) order_id = cur.fetchone()[0] assert order_id is not None

✅ user и product созданы автоматически!

Переопределение значений фабрики

Пример: задать конкретный email

def test_user_with_specific_email(db_connection): """Тест пользователя с конкретным email""" cur = db_connection.cursor() # Переопределите email user_data = UserFactory(email="alice@test.com") cur.execute( "INSERT INTO users (email, name) VALUES (%(email)s, %(name)s)", user_data ) # Проверка cur.execute("SELECT email FROM users WHERE email = %s", ("alice@test.com",)) assert cur.fetchone() is not None

Пример: заказ для конкретного пользователя

def test_order_for_specific_user(db_connection): """Тест заказа для конкретного пользователя""" cur = db_connection.cursor() # Создайте пользователя user_data = UserFactory(email="bob@test.com") cur.execute( "INSERT INTO users (email) VALUES (%(email)s) RETURNING id", user_data ) user_id = cur.fetchone()[0] # Создайте заказ для этого пользователя order_data = OrderFactory(user={"id": user_id}) # ✅ order_data["user"]["id"] == user_id

Интеграция с pytest фикстурами

Фикстура-обёртка для фабрик

# tests/conftest.py @pytest.fixture def create_user(db_connection): """Фикстура для создания пользователя""" def _create_user(**kwargs): cur = db_connection.cursor() user_data = UserFactory(**kwargs) cur.execute( "INSERT INTO users (email, name) VALUES (%(email)s, %(name)s) RETURNING id", user_data ) user_id = cur.fetchone()[0] return {"id": user_id, **user_data} return _create_user

Использование:

def test_with_fixture(create_user): """Тест с фикстурой""" user = create_user(email="test@test.com") assert user["id"] is not None assert user["email"] == "test@test.com"

Troubleshooting

"duplicate key value violates unique constraint"

Проблема: Faker сгенерировал одинаковый email дважды.

Решение: Используйте Sequence

class UserFactory(factory.Factory): email = factory.Sequence(lambda n: f"user{n}@test.com") # Генерирует: user1@test.com, user2@test.com, user3@test.com

"LazyFunction возвращает одно и то же значение"

Проблема: Вы создали fake один раз при импорте.

Решение: Создавайте fake внутри LazyFunction

# ❌ Плохо fake = Faker() name = factory.LazyFunction(fake.name) # Вызовется ОДИН раз при импорте! # ✅ Хорошо name = factory.LazyFunction(lambda: Faker().name()) # Вызовется каждый раз!

"Фабрика слишком медленная"

Проблема: LazyFunction создаёт новый Faker() каждый раз.

Решение: Создайте fake один раз, но используйте через lambda

fake = Faker() # Один раз class UserFactory(factory.Factory): email = factory.LazyFunction(lambda: fake.email()) # lambda вызывается каждый раз

Проверьте свои знания

Вопросы ещё не добавлены

Вопросы для этой подтемы ещё не добавлены.

Далее: PostgreSQL advanced: сложные сценарии