django-tdd

安装量: 1.1K
排名: #1260

安装

npx skills add https://github.com/affaan-m/everything-claude-code --skill django-tdd

Django Testing with TDD Test-driven development for Django applications using pytest, factory_boy, and Django REST Framework. When to Activate Writing new Django applications Implementing Django REST Framework APIs Testing Django models, views, and serializers Setting up testing infrastructure for Django projects TDD Workflow for Django Red-Green-Refactor Cycle

Step 1: RED - Write failing test

def test_user_creation ( ) : user = User . objects . create_user ( email = 'test@example.com' , password = 'testpass123' ) assert user . email == 'test@example.com' assert user . check_password ( 'testpass123' ) assert not user . is_staff

Step 2: GREEN - Make test pass

Create User model or factory

Step 3: REFACTOR - Improve while keeping tests green

Setup pytest Configuration

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: marks tests as slow integration: marks tests as integration tests Test Settings

config/settings/test.py

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

Disable migrations for speed

class DisableMigrations : def contains ( self , item ) : return True def getitem ( self , item ) : return None MIGRATION_MODULES = DisableMigrations ( )

Faster password hashing

PASSWORD_HASHERS

[ 'django.contrib.auth.hashers.MD5PasswordHasher' , ]

Email backend

EMAIL_BACKEND

'django.core.mail.backends.console.EmailBackend'

Celery always 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 timezone_settings ( settings ) : """Ensure consistent timezone.""" settings . TIME_ZONE = 'UTC' @pytest . fixture def user ( db ) : """Create a test user.""" return User . objects . create_user ( email = 'test@example.com' , password = 'testpass123' , username = 'testuser' ) @pytest . fixture def admin_user ( db ) : """Create an admin user.""" return User . objects . create_superuser ( email = 'admin@example.com' , password = 'adminpass123' , username = 'admin' ) @pytest . fixture def authenticated_client ( client , user ) : """Return authenticated client.""" client . force_login ( user ) return client @pytest . fixture def api_client ( ) : """Return DRF API client.""" from rest_framework . test import APIClient return APIClient ( ) @pytest . fixture def authenticated_api_client ( api_client , user ) : """Return authenticated API client.""" api_client . force_authenticate ( user = user ) return api_client Factory Boy Factory Setup

tests/factories.py

import factory from factory import fuzzy from datetime import datetime , timedelta from django . contrib . auth import get_user_model from apps . products . models import Product , Category User = get_user_model ( ) class UserFactory ( factory . django . DjangoModelFactory ) : """Factory for User model.""" class Meta : model = User email = factory . Sequence ( lambda n : f"user { n } @example.com" ) username = factory . Sequence ( lambda n : f"user { n } " ) password = factory . PostGenerationMethodCall ( 'set_password' , 'testpass123' ) first_name = factory . Faker ( 'first_name' ) last_name = factory . Faker ( 'last_name' ) is_active = True class CategoryFactory ( factory . django . DjangoModelFactory ) : """Factory for Category model.""" class Meta : model = Category name = factory . Faker ( 'word' ) slug = factory . LazyAttribute ( lambda obj : obj . name . lower ( ) ) description = factory . Faker ( 'text' ) class ProductFactory ( factory . django . DjangoModelFactory ) : """Factory for Product model.""" class Meta : model = Product name = factory . Faker ( 'sentence' , nb_words = 3 ) slug = factory . LazyAttribute ( lambda obj : obj . name . lower ( ) . replace ( ' ' , '-' ) ) description = factory . Faker ( 'text' ) price = fuzzy . FuzzyDecimal ( 10.00 , 1000.00 , 2 ) stock = fuzzy . FuzzyInteger ( 0 , 100 ) is_active = True category = factory . SubFactory ( CategoryFactory ) created_by = factory . SubFactory ( UserFactory ) @factory . post_generation def tags ( self , create , extracted , ** kwargs ) : """Add tags to product.""" if not create : return if extracted : for tag in extracted : self . tags . add ( tag ) Using Factories

tests/test_models.py

import pytest from tests . factories import ProductFactory , UserFactory def test_product_creation ( ) : """Test product creation using factory.""" product = ProductFactory ( price = 100.00 , stock = 50 ) assert product . price == 100.00 assert product . stock == 50 assert product . is_active is True def test_product_with_tags ( ) : """Test product with tags.""" tags = [ TagFactory ( name = 'electronics' ) , TagFactory ( name = 'new' ) ] product = ProductFactory ( tags = tags ) assert product . tags . count ( ) == 2 def test_multiple_products ( ) : """Test creating multiple products.""" products = ProductFactory . create_batch ( 10 ) assert len ( products ) == 10 Model Testing Model Tests

tests/test_models.py

import pytest from django . core . exceptions import ValidationError from tests . factories import UserFactory , ProductFactory class TestUserModel : """Test User model.""" def test_create_user ( self , db ) : """Test creating a regular user.""" user = UserFactory ( email = 'test@example.com' ) assert user . email == 'test@example.com' assert user . check_password ( 'testpass123' ) assert not user . is_staff assert not user . is_superuser def test_create_superuser ( self , db ) : """Test creating a superuser.""" user = UserFactory ( email = 'admin@example.com' , is_staff = True , is_superuser = True ) assert user . is_staff assert user . is_superuser def test_user_str ( self , db ) : """Test user string representation.""" user = UserFactory ( email = 'test@example.com' ) assert str ( user ) == 'test@example.com' class TestProductModel : """Test Product model.""" def test_product_creation ( self , db ) : """Test creating a product.""" product = ProductFactory ( ) assert product . id is not None assert product . is_active is True assert product . created_at is not None def test_product_slug_generation ( self , db ) : """Test automatic slug generation.""" product = ProductFactory ( name = 'Test Product' ) assert product . slug == 'test-product' def test_product_price_validation ( self , db ) : """Test price cannot be negative.""" product = ProductFactory ( price = - 10 ) with pytest . raises ( ValidationError ) : product . full_clean ( ) def test_product_manager_active ( self , db ) : """Test active manager method.""" ProductFactory . create_batch ( 5 , is_active = True ) ProductFactory . create_batch ( 3 , is_active = False ) active_count = Product . objects . active ( ) . count ( ) assert active_count == 5 def test_product_stock_management ( self , db ) : """Test stock management.""" product = ProductFactory ( stock = 10 ) product . reduce_stock ( 5 ) product . refresh_from_db ( ) assert product . stock == 5 with pytest . raises ( ValueError ) : product . reduce_stock ( 10 )

Not enough stock

View Testing Django View Testing

tests/test_views.py

import pytest from django . urls import reverse from tests . factories import ProductFactory , UserFactory class TestProductViews : """Test product views.""" def test_product_list ( self , client , db ) : """Test product list view.""" ProductFactory . create_batch ( 10 ) response = client . get ( reverse ( 'products:list' ) ) assert response . status_code == 200 assert len ( response . context [ 'products' ] ) == 10 def test_product_detail ( self , client , db ) : """Test product detail view.""" product = ProductFactory ( ) response = client . get ( reverse ( 'products:detail' , kwargs = { 'slug' : product . slug } ) ) assert response . status_code == 200 assert response . context [ 'product' ] == product def test_product_create_requires_login ( self , client , db ) : """Test product creation requires authentication.""" response = client . get ( reverse ( 'products:create' ) ) assert response . status_code == 302 assert response . url . startswith ( '/accounts/login/' ) def test_product_create_authenticated ( self , authenticated_client , db ) : """Test product creation as authenticated user.""" response = authenticated_client . get ( reverse ( 'products:create' ) ) assert response . status_code == 200 def test_product_create_post ( self , authenticated_client , db , category ) : """Test creating a product via POST.""" data = { 'name' : 'Test Product' , 'description' : 'A test product' , 'price' : '99.99' , 'stock' : 10 , 'category' : category . id , } response = authenticated_client . post ( reverse ( 'products:create' ) , data ) assert response . status_code == 302 assert Product . objects . filter ( name = 'Test Product' ) . exists ( ) DRF API Testing Serializer Testing

tests/test_serializers.py

import pytest from rest_framework . exceptions import ValidationError from apps . products . serializers import ProductSerializer from tests . factories import ProductFactory class TestProductSerializer : """Test ProductSerializer.""" def test_serialize_product ( self , db ) : """Test serializing a product.""" product = ProductFactory ( ) serializer = ProductSerializer ( product ) data = serializer . data assert data [ 'id' ] == product . id assert data [ 'name' ] == product . name assert data [ 'price' ] == str ( product . price ) def test_deserialize_product ( self , db ) : """Test deserializing product data.""" data = { 'name' : 'Test Product' , 'description' : 'Test description' , 'price' : '99.99' , 'stock' : 10 , 'category' : 1 , } serializer = ProductSerializer ( data = data ) assert serializer . is_valid ( ) product = serializer . save ( ) assert product . name == 'Test Product' assert float ( product . price ) == 99.99 def test_price_validation ( self , db ) : """Test price validation.""" data = { 'name' : 'Test Product' , 'price' : '-10.00' , 'stock' : 10 , } serializer = ProductSerializer ( data = data ) assert not serializer . is_valid ( ) assert 'price' in serializer . errors def test_stock_validation ( self , db ) : """Test stock cannot be negative.""" data = { 'name' : 'Test Product' , 'price' : '99.99' , 'stock' : - 5 , } serializer = ProductSerializer ( data = data ) assert not serializer . is_valid ( ) assert 'stock' in serializer . errors API ViewSet Testing

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 ProductFactory , UserFactory class TestProductAPI : """Test Product API endpoints.""" @pytest . fixture def api_client ( self ) : """Return API client.""" return APIClient ( ) def test_list_products ( self , api_client , db ) : """Test listing products.""" ProductFactory . create_batch ( 10 ) url = reverse ( 'api:product-list' ) response = api_client . get ( url ) assert response . status_code == status . HTTP_200_OK assert response . data [ 'count' ] == 10 def test_retrieve_product ( self , api_client , db ) : """Test retrieving a product.""" product = ProductFactory ( ) url = reverse ( 'api:product-detail' , kwargs = { 'pk' : product . id } ) response = api_client . get ( url ) assert response . status_code == status . HTTP_200_OK assert response . data [ 'id' ] == product . id def test_create_product_unauthorized ( self , api_client , db ) : """Test creating product without authentication.""" url = reverse ( 'api:product-list' ) data = { 'name' : 'Test Product' , 'price' : '99.99' } response = api_client . post ( url , data ) assert response . status_code == status . HTTP_401_UNAUTHORIZED def test_create_product_authorized ( self , authenticated_api_client , db ) : """Test creating product as authenticated user.""" url = reverse ( 'api:product-list' ) data = { 'name' : 'Test Product' , 'description' : 'Test' , 'price' : '99.99' , 'stock' : 10 , } response = authenticated_api_client . post ( url , data ) assert response . status_code == status . HTTP_201_CREATED assert response . data [ 'name' ] == 'Test Product' def test_update_product ( self , authenticated_api_client , db ) : """Test updating a product.""" product = ProductFactory ( created_by = authenticated_api_client . user ) url = reverse ( 'api:product-detail' , kwargs = { 'pk' : product . id } ) data = { 'name' : 'Updated Product' } response = authenticated_api_client . patch ( url , data ) assert response . status_code == status . HTTP_200_OK assert response . data [ 'name' ] == 'Updated Product' def test_delete_product ( self , authenticated_api_client , db ) : """Test deleting a product.""" product = ProductFactory ( created_by = authenticated_api_client . user ) url = reverse ( 'api:product-detail' , kwargs = { 'pk' : product . id } ) response = authenticated_api_client . delete ( url ) assert response . status_code == status . HTTP_204_NO_CONTENT def test_filter_products_by_price ( self , api_client , db ) : """Test filtering products by price.""" ProductFactory ( price = 50 ) ProductFactory ( price = 150 ) url = reverse ( 'api:product-list' ) response = api_client . get ( url , { 'price_min' : 100 } ) assert response . status_code == status . HTTP_200_OK assert response . data [ 'count' ] == 1 def test_search_products ( self , api_client , db ) : """Test searching products.""" ProductFactory ( name = 'Apple iPhone' ) ProductFactory ( name = 'Samsung Galaxy' ) url = reverse ( 'api:product-list' ) response = api_client . get ( url , { 'search' : 'Apple' } ) assert response . status_code == status . HTTP_200_OK assert response . data [ 'count' ] == 1 Mocking and Patching Mocking External Services

tests/test_views.py

from unittest . mock import patch , Mock import pytest class TestPaymentView : """Test payment view with mocked payment gateway.""" @patch ( 'apps.payments.services.stripe' ) def test_successful_payment ( self , mock_stripe , client , user , product ) : """Test successful payment with mocked Stripe."""

Configure mock

mock_stripe . Charge . create . return_value = { 'id' : 'ch_123' , 'status' : 'succeeded' , 'amount' : 9999 , } client . force_login ( user ) response = client . post ( reverse ( 'payments:process' ) , { 'product_id' : product . id , 'token' : 'tok_visa' , } ) assert response . status_code == 302 mock_stripe . Charge . create . assert_called_once ( ) @patch ( 'apps.payments.services.stripe' ) def test_failed_payment ( self , mock_stripe , client , user , product ) : """Test failed payment.""" mock_stripe . Charge . create . side_effect = Exception ( 'Card declined' ) client . force_login ( user ) response = client . post ( reverse ( 'payments:process' ) , { 'product_id' : product . id , 'token' : 'tok_visa' , } ) assert response . status_code == 302 assert 'error' in response . url Mocking Email Sending

tests/test_email.py

from django . core import mail from django . test import override_settings @override_settings ( EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' ) def test_order_confirmation_email ( db , order ) : """Test order confirmation email.""" order . send_confirmation_email ( ) assert len ( mail . outbox ) == 1 assert order . user . email in mail . outbox [ 0 ] . to assert 'Order Confirmation' in mail . outbox [ 0 ] . subject Integration Testing Full Flow Testing

tests/test_integration.py

import pytest from django . urls import reverse from tests . factories import UserFactory , ProductFactory class TestCheckoutFlow : """Test complete checkout flow.""" def test_guest_to_purchase_flow ( self , client , db ) : """Test complete flow from guest to purchase."""

Step 1: Register

response

client . post ( reverse ( 'users:register' ) , { 'email' : 'test@example.com' , 'password' : 'testpass123' , 'password_confirm' : 'testpass123' , } ) assert response . status_code == 302

Step 2: Login

response

client . post ( reverse ( 'users:login' ) , { 'email' : 'test@example.com' , 'password' : 'testpass123' , } ) assert response . status_code == 302

Step 3: Browse products

product

ProductFactory ( price = 100 ) response = client . get ( reverse ( 'products:detail' , kwargs = { 'slug' : product . slug } ) ) assert response . status_code == 200

Step 4: Add to cart

response

client . post ( reverse ( 'cart:add' ) , { 'product_id' : product . id , 'quantity' : 1 , } ) assert response . status_code == 302

Step 5: Checkout

response

client . get ( reverse ( 'checkout:review' ) ) assert response . status_code == 200 assert product . name in response . content . decode ( )

Step 6: Complete purchase

with
patch
(
'apps.checkout.services.process_payment'
)
as
mock_payment
:
mock_payment
.
return_value
=
True
response
=
client
.
post
(
reverse
(
'checkout:complete'
)
)
assert
response
.
status_code
==
302
assert
Order
.
objects
.
filter
(
user__email
=
'test@example.com'
)
.
exists
(
)
Testing Best Practices
DO
Use factories
Instead of manual object creation
One assertion per test
Keep tests focused
Descriptive test names
:
test_user_cannot_delete_others_post
Test edge cases
Empty inputs, None values, boundary conditions
Mock external services
Don't depend on external APIs
Use fixtures
Eliminate duplication
Test permissions
Ensure authorization works
Keep tests fast
Use
--reuse-db
and
--nomigrations
DON'T
Don't test Django internals
Trust Django to work
Don't test third-party code
Trust libraries to work
Don't ignore failing tests
All tests must pass
Don't make tests dependent
Tests should run in any order
Don't over-mock
Mock only external dependencies
Don't test private methods
Test public interface
Don't use production database
Always use test database Coverage Coverage Configuration

Run tests with coverage

pytest --cov = apps --cov-report = html --cov-report = term-missing

Generate HTML report

open htmlcov/index.html Coverage Goals Component Target Coverage Models 90%+ Serializers 85%+ Views 80%+ Services 90%+ Utilities 80%+ Overall 80%+ Quick Reference Pattern Usage @pytest.mark.django_db Enable database access client Django test client api_client DRF API client factory.create_batch(n) Create multiple objects patch('module.function') Mock external dependencies override_settings Temporarily change settings force_authenticate() Bypass authentication in tests assertRedirects Check for redirects assertTemplateUsed Verify template usage mail.outbox Check sent emails Remember: Tests are documentation. Good tests explain how your code should work. Keep them simple, readable, and maintainable.

返回排行榜