Testes Unitários Django com pytest

O test runner embutido do Django funciona, mas o pytest deixa os testes mais poderosos. Mensagens de erro melhores, parametrize para testes orientados a tabela, e um ecossistema maior de plugins para cobertura, factories e acesso ao banco.

TL;DR: Configure o pytest-django para rodar o seu test suite Django, adicione fixtures, use factories para dados de teste e obtenha relatórios de cobertura.
Stack: Python, Django, pytest, pytest-django, factory-boy
Nível: Intermediário
Tempo de leitura: ~9 min

Configurar projeto Django com DRF

pip install django djangorestframework
django-admin startproject projtest .
python3 manage.py startapp app

Criar o model Hero

class Hero(models.Model):
    name = models.CharField(max_length=100)
    canFly = models.BooleanField()
    genre = models.CharField(max_length=10)

Criar caso de uso, protocolos e controller

Organize o projeto em camadas: application/use_cases/, infra/, main/ e presentation/. O controller delega para o caso de uso, e o caso de uso fica livre de preocupações HTTP.

class ListHeroesController(APIView):
    def post(self, request):
        try:
            inbound = ListHeroesRequest()
            inbound.canFly = request.data.get("canFly", "false").lower() == "true"
            inbound.genre = request.data.get("genre", "male")
            use_case = ListHeroes()
            result = use_case.execute(inbound)
            outbound = [hero.__dict__ for hero in result]
            return Response({"data": outbound}, status=status.HTTP_200_OK)
        except Exception as e:
            return Response({"message_error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

Instalar pytest

pip install pytest pytest-django

Criar pytest.ini

[pytest]
DJANGO_SETTINGS_MODULE = projtest.settings
python_files = tests.py test_*.py *_tests.py

Escrever testes

# tests/unit/test_list_heroes.py
import pytest
from application.use_cases.list_heroes.list_heroes import ListHeroes
from application.use_cases.list_heroes.protocols.list_heroes_request import ListHeroesRequest
from app.models import Hero

@pytest.mark.django_db
def test_list_heroes_use_case():
    Hero.objects.create(name="Superman", canFly=True, genre="male")
    Hero.objects.create(name="Wonder Woman", canFly=False, genre="female")
    Hero.objects.create(name="Batman", canFly=False, genre="male")

    inbound = ListHeroesRequest()
    inbound.canFly = True
    inbound.genre = "male"

    use_case = ListHeroes()
    result = use_case.execute(inbound)

    assert len(result) == 1
    assert result[0].name == "Superman"
    assert result[0].canFly is True
pytest

Adicionar cobertura

pip install pytest-cov
pytest --cov=application --cov-report=term-missing

Crie .coveragerc para excluir arquivos de protocolo da cobertura (são containers de dados, não lógica):

[run]
omit =
    application/use_cases/*/protocols/*

O que você construiu

O pytest configurado para o seu projeto Django com testes funcionando, fixtures de banco de dados e relatório de cobertura.

Próximos passos

  • Use @pytest.mark.django_db nos testes que precisam de acesso ao banco. Sem isso, chamadas ao banco levantam um erro.
  • Use factory-boy para dados de teste: UserFactory() é mais limpo do que repetir o setup do model em cada teste.
  • Rode pytest –reuse-db com pytest-django para pular as migrations em execuções repetidas dos testes, o que acelera bastante o suite.

Dúvidas ou feedback? Me encontre no LinkedIn ou GitHub.

Deixe um comentário