Communitygithub.com

django-tdd

Estratégias de teste Django com pytest-django, metodologia TDD, factory_boy, mocks, cobertura e testes de APIs Django REST Framework. Use ao escrever testes Django ou DRF com pytest-django, ou ao conduzir uma funcionalidade Django orientada a testes.

O que é django-tdd?

Esta skill orienta o desenvolvimento de aplicações Django utilizando Test-Driven Development (TDD) com pytest-django, factory_boy e Django REST Framework. Ela fornece um fluxo de trabalho completo do ciclo Red-Green-Refactor, configuração de pytest e settings de teste otimizados (uso de banco em memória, desativação de migrações e hashing rápido de senhas). Inclui fixtures conftest.py para usuários comuns, admin, clientes autenticados e API clients, além de factories reutilizáveis com sequences, Faker e SubFactory. Também cobre testes de modelos (validação, slugs, managers), views (acesso anônimo vs. autenticado, POST e GET) e APIs DRF (serializers, ViewSets, permissões). A skill é útil para garantir qualidade e evitar regressões em projetos Django de qualquer porte, sendo compatível com qualquer ambiente que use Python e pytest.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/django-tdd

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Testes Django com TDD

Desenvolvimento orientado a testes para aplicações Django usando pytest, factory_boy e Django REST Framework.

Quando Ativar

  • Escrevendo novas aplicações Django
  • Implementando APIs Django REST Framework
  • Testando models, views e serializers do Django
  • Configurando infraestrutura de testes para projetos Django

Fluxo de Trabalho TDD para Django

Ciclo Vermelho-Verde-Refatoração

# Passo 1: VERMELHO - Escreva um teste que falha
def test_criacao_usuario():
    usuario = User.objects.create_user(email='[email protected]', senha='testesenha123')
    assert usuario.email == '[email protected]'
    assert usuario.check_password('testesenha123')
    assert not usuario.is_staff

# Passo 2: VERDE - Faça o teste passar
# Crie o model User ou a factory

# Passo 3: REFATORAR - Melhore mantendo os testes verdes

Configuração

Configuração do pytest

# pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE = config.settings.test
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
    --reuse-db
    --nomigrations
    --cov=apps
    --cov-report=html
    --cov-report=term-missing
    --strict-markers
markers =
    slow: marca testes como lentos
    integration: marca testes como de integração

Configurações de Teste

# config/settings/test.py
from .base import *

DEBUG = True
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': ':memory:',
    }
}

# Desativar migrações para ganhar velocidade
class DisableMigrations:
    def __contains__(self, item):
        return True

    def __getitem__(self, item):
        return None

MIGRATION_MODULES = DisableMigrations()

# Hash de senha mais rápido
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.MD5PasswordHasher',
]

# Backend de e-mail
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

# Celery sempre eager
CELERY_TASK_ALWAYS_EAGER = True
CELERY_TASK_EAGER_PROPAGATES = True

conftest.py

# tests/conftest.py
import pytest
from django.utils import timezone
from django.contrib.auth import get_user_model

User = get_user_model()

@pytest.fixture(autouse=True)
def configuracao_fuso_horario(settings):
    """Garantir fuso horário consistente."""
    settings.TIME_ZONE = 'UTC'

@pytest.fixture
def usuario(db):
    """Criar um usuário de teste."""
    return User.objects.create_user(
        email='[email protected]',
        password='testesenha123',
        username='usuarioteste'
    )

@pytest.fixture
def usuario_admin(db):
    """Criar um usuário admin."""
    return User.objects.create_superuser(
        email='[email protected]',
        password='adminsenha123',
        username='admin'
    )

@pytest.fixture
def cliente_autenticado(client, usuario):
    """Retornar cliente autenticado."""
    client.force_login(usuario)
    return client

@pytest.fixture
def api_client():
    """Retornar cliente DRF API."""
    from rest_framework.test import APIClient
    return APIClient()

@pytest.fixture
def api_client_autenticado(api_client, usuario):
    """Retornar cliente API autenticado."""
    api_client.force_authenticate(user=usuario)
    return api_client

Factory Boy

Configuração da Factory

# tests/factories.py
import factory
from factory import fuzzy
from datetime import datetime, timedelta
from django.contrib.auth import get_user_model
from apps.produtos.models import Produto, Categoria

User = get_user_model()

class UserFactory(factory.django.DjangoModelFactory):
    """Factory para o model User."""

    class Meta:
        model = User

    email = factory.Sequence(lambda n: f"usuario{n}@exemplo.com")
    username = factory.Sequence(lambda n: f"usuario{n}")
    password = factory.PostGenerationMethodCall('set_password', 'testesenha123')
    first_name = factory.Faker('first_name')
    last_name = factory.Faker('last_name')
    is_active = True

class CategoriaFactory(factory.django.DjangoModelFactory):
    """Factory para o model Categoria."""

    class Meta:
        model = Categoria

    nome = factory.Faker('word')
    slug = factory.LazyAttribute(lambda obj: obj.nome.lower())
    descricao = factory.Faker('text')

class ProdutoFactory(factory.django.DjangoModelFactory):
    """Factory para o model Produto."""

    class Meta:
        model = Produto

    nome = factory.Faker('sentence', nb_words=3)
    slug = factory.LazyAttribute(lambda obj: obj.nome.lower().replace(' ', '-'))
    descricao = factory.Faker('text')
    preco = fuzzy.FuzzyDecimal(10.00, 1000.00, 2)
    estoque = fuzzy.FuzzyInteger(0, 100)
    is_active = True
    categoria = factory.SubFactory(CategoriaFactory)
    criado_por = factory.SubFactory(UserFactory)

    @factory.post_generation
    def tags(self, create, extracted, **kwargs):
        """Adicionar tags ao produto."""
        if not create:
            return
        if extracted:
            for tag in extracted:
                self.tags.add(tag)

Usando Factories

# tests/test_models.py
import pytest
from tests.factories import ProdutoFactory, UserFactory

def test_criacao_produto():
    """Testar criação de produto usando factory."""
    produto = ProdutoFactory(preco=100.00, estoque=50)
    assert produto.preco == 100.00
    assert produto.estoque == 50
    assert produto.is_active is True

def test_produto_com_tags():
    """Testar produto com tags."""
    tags = [TagFactory(nome='eletrônicos'), TagFactory(nome='novo')]
    produto = ProdutoFactory(tags=tags)
    assert produto.tags.count() == 2

def test_multiplos_produtos():
    """Testar criação de múltiplos produtos."""
    produtos = ProdutoFactory.create_batch(10)
    assert len(produtos) == 10

Teste de Models

Testes de Model

# tests/test_models.py
import pytest
from django.core.exceptions import ValidationError
from tests.factories import UserFactory, ProdutoFactory

class TestUserModel:
    """Testar model User."""

    def test_criar_usuario(self, db):
        """Testar criação de um usuário comum."""
        usuario = UserFactory(email='[email protected]')
        assert usuario.email == '[email protected]'
        assert usuario.check_password('testesenha123')
        assert not usuario.is_staff
        assert not usuario.is_superuser

    def test_criar_superusuario(self, db):
        """Testar criação de um superusuário."""
        usuario = UserFactory(
            email='[email protected]',
            is_staff=True,
            is_superuser=True
        )
        assert usuario.is_staff
        assert usuario.is_superuser

    def test_str_usuario(self, db):
        """Testar representação em string do usuário."""
        usuario = UserFactory(email='[email protected]')
        assert str(usuario) == '[email protected]'

class TestProdutoModel:
    """Testar model Produto."""

    def test_criacao_produto(self, db):
        """Testar criação de um produto."""
        produto = ProdutoFactory()
        assert produto.id is not None
        assert produto.is_active is True
        assert produto.created_at is not None

    def test_geracao_slug_produto(self, db):
        """Testar geração automática de slug."""
        produto = ProdutoFactory(nome='Produto de Teste')
        assert produto.slug == 'produto-de-teste'

    def test_validacao_preco_produto(self, db):
        """Testar que o preço não pode ser negativo."""
        produto = ProdutoFactory(preco=-10)
        with pytest.raises(ValidationError):
            produto.full_clean()

    def test_gerente_ativo_produto(self, db):
        """Testar método ativo do gerente."""
        ProdutoFactory.create_batch(5, is_active=True)
        ProdutoFactory.create_batch(3, is_active=False)

        contagem_ativos = Produto.objects.active().count()
        assert contagem_ativos == 5

    def test_gerenciamento_estoque_produto(self, db):
        """Testar gerenciamento de estoque."""
        produto = ProdutoFactory(estoque=10)
        produto.reduzir_estoque(5)
        produto.refresh_from_db()
        assert produto.estoque == 5

        with pytest.raises(ValueError):
            produto.reduzir_estoque(10)  # Estoque insuficiente

Teste de Views

Teste de Views Django

# tests/test_views.py
import pytest
from django.urls import reverse
from tests.factories import ProdutoFactory, UserFactory

class TestProdutoViews:
    """Testar views de produto."""

    def test_lista_produtos(self, client, db):
        """Testar view de listagem de produtos."""
        ProdutoFactory.create_batch(10)

        resposta = client.get(reverse('produtos:lista'))

        assert resposta.status_code == 200
        assert len(resposta.context['produtos']) == 10

    def test_detalhe_produto(self, client, db):
        """Testar view de detalhe do produto."""
        produto = ProdutoFactory()

        resposta = client.get(reverse('produtos:detalhe', kwargs={'slug': produto.slug}))

        assert resposta.status_code == 200
        assert resposta.context['produto'] == produto

    def test_criacao_produto_requer_login(self, client, db):
        """Testar que criação de produto requer autenticação."""
        resposta = client.get(reverse('produtos:criar'))

        assert resposta.status_code == 302
        assert resposta.url.startswith('/accounts/login/')

    def test_criacao_produto_autenticado(self, cliente_autenticado, db):
        """Testar criação de produto como usuário autenticado."""
        resposta = cliente_autenticado.get(reverse('produtos:criar'))

        assert resposta.status_code == 200

    def test_criacao_produto_post(self, cliente_autenticado, db, categoria):
        """Testar criação de produto via POST."""
        dados = {
            'nome': 'Produto de Teste',
            'descricao': 'Um produto de teste',
            'preco': '99.99',
            'estoque': 10,
            'categoria': categoria.id,
        }

        resposta = cliente_autenticado.post(reverse('produtos:criar'), dados)

        assert resposta.status_code == 302
        assert Produto.objects.filter(nome='Produto de Teste').exists()

Teste de API DRF

Teste de Serializers

# tests/test_serializers.py
import pytest
from rest_framework.exceptions import ValidationError
from apps.produtos.serializers import ProdutoSerializer
from tests.factories import ProdutoFactory

class TestProdutoSerializer:
    """Testar ProdutoSerializer."""

    def test_serializar_produto(self, db):
        """Testar serialização de um produto."""
        produto = ProdutoFactory()
        serializer = ProdutoSerializer(produto)

        dados = serializer.data

        assert dados['id'] == produto.id
        assert dados['nome'] == produto.nome
        assert dados['preco'] == str(produto.preco)

    def test_desserializar_produto(self, db):
        """Testar desserialização de dados do produto."""
        dados = {
            'nome': 'Produto de Teste',
            'descricao': 'Descrição de teste',
            'preco': '99.99',
            'estoque': 10,
            'categoria': 1,
        }

        serializer = ProdutoSerializer(data=dados)

        assert serializer.is_valid()
        produto = serializer.save()

        assert produto.nome == 'Produto de Teste'
        assert float(produto.preco) == 99.99

    def test_validacao_preco(self, db):
        """Testar validação de preço."""
        dados = {
            'nome': 'Produto de Teste',
            'preco': '-10.00',
            'estoque': 10,
        }

        serializer = ProdutoSerializer(data=dados)

        assert not serializer.is_valid()
        assert 'preco' in serializer.errors

    def test_validacao_estoque(self, db):
        """Testar que estoque não pode ser negativo."""
        dados = {
            'nome': 'Produto de Teste',
            'preco': '99.99',
            'estoque': -5,
        }

        serializer = ProdutoSerializer(data=dados)

        assert not serializer.is_valid()
        assert 'estoque' in serializer.errors

Teste de ViewSets da API

# tests/test_api.py
import pytest
from rest_framework.test import APIClient
from rest_framework import status
from django.urls import reverse
from tests.factories import ProdutoFactory, UserFactory

class TestProdutoAPI:
    """Testar endpoints da API de Produto."""

    @pytest.fixture
    def api_client(self):
        """Retornar cliente API."""
        return APIClient()

    def test_listar_produtos(self, api_client, db):
        """Testar listagem de produtos."""
        ProdutoFactory.create_batch(10)

        url = reverse('api:produto-list')
        resposta = api_client.get(url)

        assert resposta.status_code == status.HTTP_200_OK
        assert resposta.data['count'] == 10

    def test_obter_produto(self, api_client, db):
        """Testar obtenção de um produto."""
        produto = ProdutoFactory()

        url = reverse('api:produto-detail', kwargs={'pk': produto.id})
        resposta = api_client.get(url)

        assert resposta.status_code == status.HTTP_200_OK
        assert resposta.data['id'] == produto.id

    def test_criar_produto_nao_autorizado(self, api_client, db):
        """Testar criação de produto sem autenticação."""
        url = reverse('api:produto-list')
        dados = {'nome': 'Produto de Teste', 'preco': '99.99'}

        resposta = api_client.post(url, dados)

        assert resposta.status_code == status.HTTP_401_UNAUTHORIZED

    def test_criar_produto_autorizado(self, api_client_autenticado, db):
        """Testar criação de produto como usuário autenticado."""
        url = reverse('api:produto-list')
        dados = {
            'nome': 'Produto de Teste',
            'descricao': 'Teste',
            'preco': '99.99',
            'estoque': 10,
        }

        resposta = api_client_autenticado.post(url, dados)

        assert resposta.status_code == status.HTTP_201_CREATED
        assert resposta.data['nome'] == 'Produto de Teste'

    def test_atualizar_produto(self, api_client_autenticado, db):
        """Testar atualização de um produto."""
        produto = ProdutoFactory(criado_por=api_client_autenticado.usuario)

        url = reverse('api:produto-detail', kwargs={'pk': produto.id})
        dados = {'nome': 'Produto Atualizado'}

        resposta = api_client_autenticado.patch(url, dados)

        assert resposta.status_code == status.HTTP_200_OK
        assert resposta.data['nome'] == 'Produto Atualizado'

    def test_excluir_produto(self, api_client_autenticado, db):
        """Testar exclusão de um produto."""
        produto = ProdutoFactory(criado_por=api_client_autenticado.usuario)

        url = reverse('api:produto-detail', kwargs={'pk': produto.id})
        resposta = api_client_autenticado.delete(url)

        assert resposta.status_code == status.HTTP_204_NO_CONTENT
        assert not Produto.objects.filter(id=produto.id).exists()

Boas Práticas

Estrutura de Testes

tests/
├── __init__.py
├── conftest.py          # Fixtures compartilhadas
├── factories.py         # Definitions de fábrica
├── test_models.py       # Testes de model
├── test_views.py        # Testes de view
├── test_serializers.py  # Testes de serializer
├── test_api.py          # Testes de API
└── test_forms.py        # Testes de formulário

Padrões de Nomenclatura

  • Arquivos de teste: test_<modulo>.py
  • Classes de teste: Test<nome do Modelo> ou Test<nome da View>
  • Métodos de teste: test_<comportamento ou cenário>
  • Factories: <Nome>Factory

Marcadores de Cobertura

import pytest

pytestmark = pytest.mark.django_db  # Aplica a todos os testes no arquivo

class TestProdutoModel:
    """Testes de model para Produto."""

    pytestmark = pytest.mark.unit  # Aplica marcador unit a todos os testes

    def test_algo_rapido(self):
        """Teste rápido - não requer banco."""
        assert 1 + 1 == 2

    def test_com_banco(self, db):
        """Teste que precisa do banco."""
        produto = ProdutoFactory()
        assert produto.id is not None

Dicas de Testes

  1. Teste uma coisa por vez - Cada teste deve verificar um comportamento específico
  2. Use nomes descritivos - Nomes dos testes devem explicar o que está sendo testado
  3. Mantenha os testes rápidos - Use :memory: banco de dados e desative migrações
  4. Isolar testes - Não compartilhe estado entre testes
  5. Testar casos extremos - Testes não apenas para caminho feliz, mas também para entradas inválidas
  6. Usar factories - Evite criar dados manualmente; use Factory Boy
  7. Evite lógica nos testes - Os testes devem ser simples e diretos
  8. Escrever testes primeiro - Siga o ciclo Vermelho-Verde-Refatoração

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

Habilidades Relacionadas