Volver al inicio

Fábricas factory_boy en pruebas Django pytest

El artículo demuestra el uso de factory_boy para automatizar la creación de objetos Django relacionados en pruebas pytest. Cubre SubFactory para ForeignKey, post_generation para ManyToMany, e integración con fixtures. Ejemplos cubren escenarios típicos de pruebas de modelos de biblioteca.

factory_boy: fábricas para pruebas Django confiables
Advertisement 728x90

Pruebas de modelos Django con factory_boy y pytest

Las fábricas de factory_boy simplifican la generación de instancias relacionadas de modelos Django en pruebas. En lugar de crear manualmente decenas de modelos con relaciones ForeignKey y ManyToMany, las fábricas aprovechan Faker para generar automáticamente datos de prueba realistas y variados. Esto acelera la escritura de pruebas, elimina la duplicación de código repetitivo y reduce los fallos cuando los modelos evolucionan.

Veamos un dominio de biblioteca: Genre, Language, Author y Book (con sus relaciones).

class Genre(models.Model):
    name = models.CharField(max_length=200)

class Language(models.Model):
    name = models.CharField(max_length=20)

class Author(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    date_of_birth = models.DateField(null=True, blank=True)
    date_of_death = models.DateField(null=True, blank=True)

class Book(models.Model):
    title = models.CharField(max_length=200)
    genre = models.ForeignKey('Genre', on_delete=models.CASCADE, null=True)
    language = models.ForeignKey('Language', on_delete=models.CASCADE, null=True)
    author = models.ManyToManyField('Author')
    summary = models.TextField(max_length=1000)
    isbn = models.CharField(max_length=13)

El antipatrón: creación manual de objetos

Instanciar manualmente objetos relacionados introduce graves problemas de mantenibilidad:

Google AdInline article slot
  • Código repetitivo en todas las pruebas
  • Valores codificados de forma rígida, en lugar de generación dinámica y realista de datos
  • Confusión entre la configuración de datos de prueba y la lógica de aserciones
  • Pruebas frágiles que fallan ante cambios mínimos en los modelos

Ejemplo de código problemático:

def test_book_creation():
    genre = Genre.objects.create(name="Ciencia ficción")
    language = Language.objects.create(name="Ruso")
    author = Author.objects.create(
        first_name="Arkadi",
        last_name="Strugatski",
        date_of_birth="1925-08-28"
    )
    book = Book.objects.create(
        title="Picnic a orillas del camino",
        genre=genre,
        language=language,
        summary="Una de las novelas cortas más aclamadas...",
        isbn="9785171180975"
    )
    book.author.add(author)
    assert book.display_author() == "Strugatski"

Este enfoque no escala: se vuelve inmanejable y propenso a errores en escenarios complejos con decenas de objetos interrelacionados.

Fábricas básicas con Faker

factory_boy se integra sin esfuerzo con Django y Faker para generar datos de prueba coherentes y realistas:

Google AdInline article slot
class AuthorFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Author

    first_name = factory.Faker("first_name")
    last_name = factory.Faker("last_name")
    date_of_birth = factory.Faker("date_time")
    date_of_death = factory.Faker("date_time")

Ahora crear un autor es tan sencillo como una línea limpia:

author = AuthorFactory.create()

El mismo patrón se aplica a otros modelos:

class GenreFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Genre
    name = factory.Faker("name")

class LanguageFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Language
    name = factory.Faker("name")

Manejo de modelos relacionados

Para campos ForeignKey, usa SubFactory:

Google AdInline article slot
class BookFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Book

    title = factory.Faker("sentence")
    genre = factory.SubFactory(GenreFactory)
    language = factory.SubFactory(LanguageFactory)
    summary = factory.Faker("paragraph")
    isbn = str(random.randint(1000000000000, 9999999999999))

Para ManyToManyField, usa @factory.post_generation:

@factory.post_generation
def author(self, create, extracted, **kwargs):
    if not create or not extracted:
        return
    self.author.add(*extracted)

Una prueba clara y legible:

def test_book_creation():
    author1 = AuthorFactory.create()
    author2 = AuthorFactory.create()
    book = BookFactory.create(author=[author1, author2])
    assert len(book.author.all()) == 2

Integración con pytest mediante fixtures

En conftest.py, define un fixture reutilizable:

@pytest.fixture(scope='function')
def test_book_factory() -> Book:
    author1: Author = AuthorFactory.create()
    author2: Author = AuthorFactory.create()
    book_create: Book = BookFactory.create(author=[author1, author2])
    return book_create

Luego, en test_models.py:

@pytest.mark.django_db
def test_model_book(test_book_factory: Book) -> None:
    title: str = test_book_factory.title
    genre: Genre = test_book_factory.genre
    language: Language = test_book_factory.language
    summary: str = test_book_factory.summary
    isbn: str = test_book_factory.isbn
    authors: QuerySet = test_book_factory.author.all()

    assert len(summary) <= 1000
    assert len(title) <= 200
    assert len(isbn) <= 13
    assert isinstance(title, str)
    assert isinstance(genre, Genre)
    assert isinstance(language, Language)
    assert isinstance(summary, str)
    assert isinstance(isbn, str)

    for author in authors:
        assert isinstance(author, Author)

El decorador @pytest.mark.django_db inicia una base de datos temporal y aislada para cada prueba, garantizando independencia total entre ellas.

Conclusiones clave

  • SubFactory crea automáticamente objetos asociados de tipo ForeignKey bajo demanda
  • @post_generation gestiona relaciones ManyToManyField, aceptando una lista de instancias ya creadas
  • Los fixtures de pytest separan con claridad la configuración de datos de la lógica de aserciones
  • Faker genera datos diversos y realistas, mejorando la cobertura de casos límite
  • Los cambios en los modelos solo requieren actualizaciones en las fábricas — no en docenas de archivos de prueba dispersos

— Editorial Team

Advertisement 728x90

Leer después