767 lines
22 KiB
Markdown
767 lines
22 KiB
Markdown
---
|
|
name: lp-testing
|
|
description: Creates tests for league-planner using Django TestCase with multi-database support, CRUD testing tags, middleware modification, and API testing patterns. Use for writing tests.
|
|
argument-hint: <test-type> <app-or-model>
|
|
allowed-tools: Read, Write, Edit, Glob, Grep
|
|
---
|
|
|
|
# League-Planner Testing Guide
|
|
|
|
Creates comprehensive tests following league-planner patterns: Django TestCase with multi-database support, tagged test organization, middleware modification for isolation, API testing with DRF, and factory patterns.
|
|
|
|
## When to Use
|
|
|
|
- Writing unit tests for models and business logic
|
|
- Creating integration tests for views and APIs
|
|
- Testing Celery tasks
|
|
- Implementing CRUD operation tests
|
|
- Setting up test fixtures and factories
|
|
|
|
## Test Structure
|
|
|
|
```
|
|
common/tests/
|
|
├── test_crud.py # CRUD operations (tag: crud)
|
|
├── test_gui.py # GUI/Template tests
|
|
├── test_selenium.py # Browser automation
|
|
├── __init__.py
|
|
|
|
api/tests/
|
|
├── tests.py # API endpoint tests
|
|
├── __init__.py
|
|
|
|
scheduler/
|
|
├── tests.py # Model and helper tests
|
|
├── __init__.py
|
|
|
|
{app}/tests/
|
|
├── test_models.py # Model tests
|
|
├── test_views.py # View tests
|
|
├── test_api.py # API tests
|
|
├── test_tasks.py # Celery task tests
|
|
├── factories.py # Test factories
|
|
└── __init__.py
|
|
```
|
|
|
|
## Instructions
|
|
|
|
### Step 1: Create Test Base Class
|
|
|
|
```python
|
|
# common/tests/base.py
|
|
|
|
from django.test import TestCase, TransactionTestCase, override_settings, modify_settings
|
|
from django.contrib.auth import get_user_model
|
|
from rest_framework.test import APITestCase, APIClient
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class BaseTestCase(TestCase):
|
|
"""Base test case with common setup."""
|
|
|
|
# Use all databases for multi-DB support
|
|
databases = '__all__'
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
"""Set up data for the whole test class."""
|
|
# Create test user
|
|
cls.user = User.objects.create_user(
|
|
username='testuser',
|
|
email='test@example.com',
|
|
password='testpass123',
|
|
)
|
|
cls.admin_user = User.objects.create_superuser(
|
|
username='admin',
|
|
email='admin@example.com',
|
|
password='adminpass123',
|
|
)
|
|
|
|
def setUp(self):
|
|
"""Set up for each test method."""
|
|
self.client.login(username='testuser', password='testpass123')
|
|
|
|
|
|
class BaseAPITestCase(APITestCase):
|
|
"""Base API test case."""
|
|
|
|
databases = '__all__'
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
cls.user = User.objects.create_user(
|
|
username='apiuser',
|
|
email='api@example.com',
|
|
password='apipass123',
|
|
)
|
|
cls.admin_user = User.objects.create_superuser(
|
|
username='apiadmin',
|
|
email='apiadmin@example.com',
|
|
password='adminpass123',
|
|
)
|
|
|
|
def setUp(self):
|
|
self.client = APIClient()
|
|
|
|
def authenticate_user(self):
|
|
"""Authenticate as regular user."""
|
|
self.client.force_authenticate(user=self.user)
|
|
|
|
def authenticate_admin(self):
|
|
"""Authenticate as admin."""
|
|
self.client.force_authenticate(user=self.admin_user)
|
|
|
|
|
|
@modify_settings(MIDDLEWARE={
|
|
'remove': [
|
|
'common.middleware.LoginRequiredMiddleware',
|
|
'common.middleware.AdminMiddleware',
|
|
'common.middleware.URLMiddleware',
|
|
'common.middleware.MenuMiddleware',
|
|
'common.middleware.ErrorHandlerMiddleware',
|
|
]
|
|
})
|
|
class IsolatedTestCase(BaseTestCase):
|
|
"""Test case with middleware removed for isolation."""
|
|
pass
|
|
```
|
|
|
|
### Step 2: Create Test Factories
|
|
|
|
```python
|
|
# scheduler/tests/factories.py
|
|
|
|
import factory
|
|
from factory.django import DjangoModelFactory
|
|
from django.contrib.auth import get_user_model
|
|
from scheduler.models import League, Season, Team, Scenario, Match, Day
|
|
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class UserFactory(DjangoModelFactory):
|
|
class Meta:
|
|
model = User
|
|
|
|
username = factory.Sequence(lambda n: f'user{n}')
|
|
email = factory.LazyAttribute(lambda obj: f'{obj.username}@example.com')
|
|
password = factory.PostGenerationMethodCall('set_password', 'testpass123')
|
|
|
|
|
|
class LeagueFactory(DjangoModelFactory):
|
|
class Meta:
|
|
model = League
|
|
|
|
name = factory.Sequence(lambda n: f'Test League {n}')
|
|
abbreviation = factory.Sequence(lambda n: f'TL{n}')
|
|
sport = 'football'
|
|
country = 'DE'
|
|
|
|
|
|
class SeasonFactory(DjangoModelFactory):
|
|
class Meta:
|
|
model = Season
|
|
|
|
league = factory.SubFactory(LeagueFactory)
|
|
name = factory.Sequence(lambda n: f'Season {n}')
|
|
start_date = factory.Faker('date_this_year')
|
|
end_date = factory.Faker('date_this_year')
|
|
num_teams = 18
|
|
num_rounds = 34
|
|
|
|
|
|
class TeamFactory(DjangoModelFactory):
|
|
class Meta:
|
|
model = Team
|
|
|
|
season = factory.SubFactory(SeasonFactory)
|
|
name = factory.Sequence(lambda n: f'Team {n}')
|
|
abbreviation = factory.Sequence(lambda n: f'T{n:02d}')
|
|
city = factory.Faker('city')
|
|
latitude = factory.Faker('latitude')
|
|
longitude = factory.Faker('longitude')
|
|
|
|
|
|
class ScenarioFactory(DjangoModelFactory):
|
|
class Meta:
|
|
model = Scenario
|
|
|
|
season = factory.SubFactory(SeasonFactory)
|
|
name = factory.Sequence(lambda n: f'Scenario {n}')
|
|
is_active = True
|
|
|
|
|
|
class DayFactory(DjangoModelFactory):
|
|
class Meta:
|
|
model = Day
|
|
|
|
scenario = factory.SubFactory(ScenarioFactory)
|
|
number = factory.Sequence(lambda n: n + 1)
|
|
date = factory.Faker('date_this_year')
|
|
|
|
|
|
class MatchFactory(DjangoModelFactory):
|
|
class Meta:
|
|
model = Match
|
|
|
|
scenario = factory.SubFactory(ScenarioFactory)
|
|
home_team = factory.SubFactory(TeamFactory)
|
|
away_team = factory.SubFactory(TeamFactory)
|
|
day = factory.SubFactory(DayFactory)
|
|
```
|
|
|
|
### Step 3: Write Model Tests
|
|
|
|
```python
|
|
# scheduler/tests/test_models.py
|
|
|
|
from django.test import TestCase, tag
|
|
from django.db import IntegrityError
|
|
from django.core.exceptions import ValidationError
|
|
|
|
from scheduler.models import League, Season, Team, Scenario, Match
|
|
from .factories import (
|
|
LeagueFactory, SeasonFactory, TeamFactory,
|
|
ScenarioFactory, MatchFactory, DayFactory,
|
|
)
|
|
|
|
|
|
class LeagueModelTest(TestCase):
|
|
"""Tests for League model."""
|
|
|
|
databases = '__all__'
|
|
|
|
def test_create_league(self):
|
|
"""Test basic league creation."""
|
|
league = LeagueFactory()
|
|
self.assertIsNotNone(league.pk)
|
|
self.assertTrue(league.name.startswith('Test League'))
|
|
|
|
def test_league_str(self):
|
|
"""Test string representation."""
|
|
league = LeagueFactory(name='Bundesliga')
|
|
self.assertEqual(str(league), 'Bundesliga')
|
|
|
|
def test_league_managers_relation(self):
|
|
"""Test managers M2M relationship."""
|
|
from django.contrib.auth import get_user_model
|
|
User = get_user_model()
|
|
|
|
league = LeagueFactory()
|
|
user = User.objects.create_user('manager', 'manager@test.com', 'pass')
|
|
|
|
league.managers.add(user)
|
|
self.assertIn(user, league.managers.all())
|
|
|
|
|
|
class SeasonModelTest(TestCase):
|
|
"""Tests for Season model."""
|
|
|
|
databases = '__all__'
|
|
|
|
def test_create_season(self):
|
|
"""Test season creation with league."""
|
|
season = SeasonFactory()
|
|
self.assertIsNotNone(season.pk)
|
|
self.assertIsNotNone(season.league)
|
|
|
|
def test_season_team_count(self):
|
|
"""Test team counting on season."""
|
|
season = SeasonFactory()
|
|
TeamFactory.create_batch(5, season=season)
|
|
self.assertEqual(season.teams.count(), 5)
|
|
|
|
|
|
class ScenarioModelTest(TestCase):
|
|
"""Tests for Scenario model."""
|
|
|
|
databases = '__all__'
|
|
|
|
def test_create_scenario(self):
|
|
"""Test scenario creation."""
|
|
scenario = ScenarioFactory()
|
|
self.assertIsNotNone(scenario.pk)
|
|
self.assertTrue(scenario.is_active)
|
|
|
|
@tag('slow')
|
|
def test_scenario_copy(self):
|
|
"""Test deep copy of scenario."""
|
|
from scheduler.helpers import copy_scenario
|
|
|
|
# Create scenario with matches
|
|
scenario = ScenarioFactory()
|
|
day = DayFactory(scenario=scenario)
|
|
home = TeamFactory(season=scenario.season)
|
|
away = TeamFactory(season=scenario.season)
|
|
MatchFactory(scenario=scenario, home_team=home, away_team=away, day=day)
|
|
|
|
# Copy scenario
|
|
new_scenario = copy_scenario(scenario, suffix='_copy')
|
|
|
|
self.assertNotEqual(scenario.pk, new_scenario.pk)
|
|
self.assertEqual(new_scenario.matches.count(), 1)
|
|
self.assertIn('_copy', new_scenario.name)
|
|
|
|
|
|
class MatchModelTest(TestCase):
|
|
"""Tests for Match model."""
|
|
|
|
databases = '__all__'
|
|
|
|
def test_create_match(self):
|
|
"""Test match creation."""
|
|
match = MatchFactory()
|
|
self.assertIsNotNone(match.pk)
|
|
self.assertNotEqual(match.home_team, match.away_team)
|
|
|
|
def test_match_constraint_different_teams(self):
|
|
"""Test that home and away team must be different."""
|
|
scenario = ScenarioFactory()
|
|
team = TeamFactory(season=scenario.season)
|
|
|
|
with self.assertRaises(IntegrityError):
|
|
Match.objects.create(
|
|
scenario=scenario,
|
|
home_team=team,
|
|
away_team=team, # Same team!
|
|
)
|
|
|
|
def test_match_optimized_query(self):
|
|
"""Test optimized query method."""
|
|
scenario = ScenarioFactory()
|
|
MatchFactory.create_batch(10, scenario=scenario)
|
|
|
|
with self.assertNumQueries(1):
|
|
matches = list(Match.get_for_scenario(scenario.pk))
|
|
# Access related fields
|
|
for m in matches:
|
|
_ = m.home_team.name
|
|
_ = m.away_team.name
|
|
```
|
|
|
|
### Step 4: Write View Tests
|
|
|
|
```python
|
|
# scheduler/tests/test_views.py
|
|
|
|
from django.test import TestCase, Client, tag, override_settings, modify_settings
|
|
from django.urls import reverse
|
|
from django.contrib.auth import get_user_model
|
|
|
|
from scheduler.models import League, Season, Scenario
|
|
from .factories import LeagueFactory, SeasonFactory, ScenarioFactory, UserFactory
|
|
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
@tag('crud')
|
|
@modify_settings(MIDDLEWARE={
|
|
'remove': [
|
|
'common.middleware.LoginRequiredMiddleware',
|
|
'common.middleware.URLMiddleware',
|
|
'common.middleware.MenuMiddleware',
|
|
]
|
|
})
|
|
class CRUDViewsTest(TestCase):
|
|
"""Test CRUD views for scheduler models."""
|
|
|
|
databases = '__all__'
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
cls.user = User.objects.create_superuser(
|
|
username='admin',
|
|
email='admin@test.com',
|
|
password='adminpass',
|
|
)
|
|
|
|
def setUp(self):
|
|
self.client = Client()
|
|
self.client.login(username='admin', password='adminpass')
|
|
|
|
def test_league_create(self):
|
|
"""Test league creation via view."""
|
|
url = reverse('league-add')
|
|
data = {
|
|
'name': 'New Test League',
|
|
'abbreviation': 'NTL',
|
|
'sport': 'football',
|
|
}
|
|
|
|
response = self.client.post(url, data)
|
|
|
|
self.assertEqual(response.status_code, 302) # Redirect on success
|
|
self.assertTrue(League.objects.filter(name='New Test League').exists())
|
|
|
|
def test_league_update(self):
|
|
"""Test league update via view."""
|
|
league = LeagueFactory()
|
|
url = reverse('league-edit', kwargs={'pk': league.pk})
|
|
data = {
|
|
'name': 'Updated League Name',
|
|
'abbreviation': league.abbreviation,
|
|
'sport': league.sport,
|
|
}
|
|
|
|
response = self.client.post(url, data)
|
|
|
|
self.assertEqual(response.status_code, 302)
|
|
league.refresh_from_db()
|
|
self.assertEqual(league.name, 'Updated League Name')
|
|
|
|
def test_league_delete(self):
|
|
"""Test league deletion via view."""
|
|
league = LeagueFactory()
|
|
url = reverse('league-delete', kwargs={'pk': league.pk})
|
|
|
|
response = self.client.post(url)
|
|
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertFalse(League.objects.filter(pk=league.pk).exists())
|
|
|
|
def test_scenario_list_view(self):
|
|
"""Test scenario listing view."""
|
|
season = SeasonFactory()
|
|
ScenarioFactory.create_batch(3, season=season)
|
|
|
|
# Set session
|
|
session = self.client.session
|
|
session['league'] = season.league.pk
|
|
session['season'] = season.pk
|
|
session.save()
|
|
|
|
url = reverse('scenarios')
|
|
response = self.client.get(url)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(len(response.context['scenarios']), 3)
|
|
|
|
|
|
class PermissionViewsTest(TestCase):
|
|
"""Test view permission checks."""
|
|
|
|
databases = '__all__'
|
|
|
|
def setUp(self):
|
|
self.client = Client()
|
|
self.regular_user = User.objects.create_user(
|
|
username='regular',
|
|
email='regular@test.com',
|
|
password='pass123',
|
|
)
|
|
self.staff_user = User.objects.create_user(
|
|
username='staff',
|
|
email='staff@test.com',
|
|
password='pass123',
|
|
is_staff=True,
|
|
)
|
|
self.league = LeagueFactory()
|
|
self.league.managers.add(self.staff_user)
|
|
|
|
def test_unauthorized_access_redirects(self):
|
|
"""Test that unauthorized users are redirected."""
|
|
url = reverse('league-edit', kwargs={'pk': self.league.pk})
|
|
|
|
self.client.login(username='regular', password='pass123')
|
|
response = self.client.get(url)
|
|
|
|
self.assertIn(response.status_code, [302, 403])
|
|
|
|
def test_manager_can_access(self):
|
|
"""Test that league managers can access."""
|
|
url = reverse('league-edit', kwargs={'pk': self.league.pk})
|
|
|
|
self.client.login(username='staff', password='pass123')
|
|
# Set session
|
|
session = self.client.session
|
|
session['league'] = self.league.pk
|
|
session.save()
|
|
|
|
response = self.client.get(url)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
```
|
|
|
|
### Step 5: Write API Tests
|
|
|
|
```python
|
|
# api/tests/tests.py
|
|
|
|
from django.test import tag
|
|
from rest_framework.test import APITestCase, APIClient
|
|
from rest_framework import status
|
|
from django.contrib.auth import get_user_model
|
|
|
|
from scheduler.models import Team, Season
|
|
from scheduler.tests.factories import (
|
|
SeasonFactory, TeamFactory, ScenarioFactory,
|
|
)
|
|
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class TeamsAPITest(APITestCase):
|
|
"""Test Teams API endpoints."""
|
|
|
|
databases = '__all__'
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
cls.user = User.objects.create_user(
|
|
username='apiuser',
|
|
email='api@test.com',
|
|
password='apipass123',
|
|
)
|
|
cls.season = SeasonFactory()
|
|
cls.teams = TeamFactory.create_batch(5, season=cls.season)
|
|
|
|
def setUp(self):
|
|
self.client = APIClient()
|
|
|
|
def test_list_teams_unauthenticated(self):
|
|
"""Test listing teams without authentication."""
|
|
url = f'/api/uefa/v2/teams/?season_id={self.season.pk}'
|
|
response = self.client.get(url)
|
|
|
|
# Public endpoint should work
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertEqual(len(response.data), 5)
|
|
|
|
def test_list_teams_with_filter(self):
|
|
"""Test filtering teams by active status."""
|
|
# Deactivate some teams
|
|
Team.objects.filter(pk__in=[self.teams[0].pk, self.teams[1].pk]).update(is_active=False)
|
|
|
|
url = f'/api/uefa/v2/teams/?season_id={self.season.pk}&active_only=true'
|
|
response = self.client.get(url)
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertEqual(len(response.data), 3)
|
|
|
|
def test_list_teams_missing_season(self):
|
|
"""Test error when season_id is missing."""
|
|
url = '/api/uefa/v2/teams/'
|
|
response = self.client.get(url)
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
self.assertIn('error', response.data)
|
|
|
|
def test_team_token_authentication(self):
|
|
"""Test authentication with team token."""
|
|
team = self.teams[0]
|
|
team.hashval = 'test-token-12345'
|
|
team.save()
|
|
|
|
url = f'/api/uefa/v2/team/{team.pk}/schedule/'
|
|
self.client.credentials(HTTP_X_TEAM_TOKEN='test-token-12345')
|
|
response = self.client.get(url)
|
|
|
|
self.assertIn(response.status_code, [status.HTTP_200_OK, status.HTTP_404_NOT_FOUND])
|
|
|
|
|
|
@tag('api')
|
|
class DrawsAPITest(APITestCase):
|
|
"""Test Draws API endpoints."""
|
|
|
|
databases = '__all__'
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
cls.user = User.objects.create_superuser(
|
|
username='admin',
|
|
email='admin@test.com',
|
|
password='adminpass',
|
|
)
|
|
cls.season = SeasonFactory()
|
|
|
|
def setUp(self):
|
|
self.client = APIClient()
|
|
self.client.force_authenticate(user=self.user)
|
|
|
|
def test_create_draw(self):
|
|
"""Test creating a new draw."""
|
|
url = '/api/uefa/v2/draws/create/'
|
|
data = {
|
|
'season_id': self.season.pk,
|
|
'name': 'Test Draw',
|
|
'mode': 'groups',
|
|
}
|
|
|
|
response = self.client.post(url, data, format='json')
|
|
|
|
self.assertIn(response.status_code, [status.HTTP_201_CREATED, status.HTTP_200_OK])
|
|
```
|
|
|
|
### Step 6: Write Celery Task Tests
|
|
|
|
```python
|
|
# scheduler/tests/test_tasks.py
|
|
|
|
from django.test import TestCase, TransactionTestCase, override_settings, tag
|
|
from unittest.mock import patch, MagicMock
|
|
from celery.contrib.testing.worker import start_worker
|
|
|
|
from scheduler.models import Scenario
|
|
from .factories import ScenarioFactory, TeamFactory, MatchFactory, DayFactory
|
|
|
|
|
|
@tag('celery', 'slow')
|
|
class CeleryTaskTest(TransactionTestCase):
|
|
"""Test Celery tasks."""
|
|
|
|
databases = '__all__'
|
|
|
|
def test_optimization_task_sync(self):
|
|
"""Test optimization task in synchronous mode."""
|
|
from scheduler.solver.tasks import task_optimize_scenario
|
|
|
|
scenario = ScenarioFactory()
|
|
home = TeamFactory(season=scenario.season)
|
|
away = TeamFactory(season=scenario.season)
|
|
day = DayFactory(scenario=scenario)
|
|
MatchFactory(scenario=scenario, home_team=home, away_team=away, day=day)
|
|
|
|
# Run task synchronously
|
|
result = task_optimize_scenario(
|
|
scenario_id=scenario.pk,
|
|
options={'time_limit': 10},
|
|
)
|
|
|
|
self.assertIn(result['status'], ['optimal', 'feasible', 'timeout', 'error'])
|
|
|
|
@patch('scheduler.solver.optimizer.PuLPOptimizer')
|
|
def test_optimization_task_mocked(self, MockOptimizer):
|
|
"""Test optimization task with mocked solver."""
|
|
from scheduler.solver.tasks import task_optimize_scenario
|
|
from scheduler.solver.optimizer import OptimizationResult
|
|
|
|
# Setup mock
|
|
mock_instance = MockOptimizer.return_value
|
|
mock_instance.solve.return_value = OptimizationResult(
|
|
status='optimal',
|
|
objective_value=100.0,
|
|
solution={'x': {(1, 1, 1): 1.0}},
|
|
solve_time=1.5,
|
|
gap=0.0,
|
|
iterations=100,
|
|
)
|
|
|
|
scenario = ScenarioFactory()
|
|
|
|
result = task_optimize_scenario(scenario_id=scenario.pk)
|
|
|
|
self.assertEqual(result['status'], 'optimal')
|
|
mock_instance.build_model.assert_called_once()
|
|
mock_instance.solve.assert_called_once()
|
|
|
|
def test_task_abort(self):
|
|
"""Test task abort handling."""
|
|
from scheduler.solver.tasks import task_optimize_scenario
|
|
from unittest.mock import PropertyMock
|
|
|
|
scenario = ScenarioFactory()
|
|
|
|
# Create mock task with is_aborted returning True
|
|
with patch.object(task_optimize_scenario, 'is_aborted', return_value=True):
|
|
# This would require more setup to properly test
|
|
pass
|
|
|
|
|
|
class TaskProgressTest(TestCase):
|
|
"""Test task progress tracking."""
|
|
|
|
databases = '__all__'
|
|
|
|
def test_task_record_creation(self):
|
|
"""Test TaskRecord is created on task start."""
|
|
from taskmanager.models import Task as TaskRecord
|
|
|
|
initial_count = TaskRecord.objects.count()
|
|
|
|
# Simulate task creating record
|
|
record = TaskRecord.objects.create(
|
|
task_id='test-task-id',
|
|
task_name='test.task',
|
|
scenario_id=1,
|
|
)
|
|
|
|
self.assertEqual(TaskRecord.objects.count(), initial_count + 1)
|
|
self.assertEqual(record.progress, 0)
|
|
|
|
def test_task_progress_update(self):
|
|
"""Test progress update."""
|
|
from taskmanager.models import Task as TaskRecord
|
|
|
|
record = TaskRecord.objects.create(
|
|
task_id='test-task-id',
|
|
task_name='test.task',
|
|
)
|
|
|
|
record.update_progress(50, 'Halfway done')
|
|
record.refresh_from_db()
|
|
|
|
self.assertEqual(record.progress, 50)
|
|
self.assertEqual(record.status_message, 'Halfway done')
|
|
```
|
|
|
|
## Running Tests
|
|
|
|
```bash
|
|
# Run all tests
|
|
python manage.py test
|
|
|
|
# Run with tag
|
|
python manage.py test --tag crud
|
|
python manage.py test --tag api
|
|
python manage.py test --exclude-tag slow
|
|
|
|
# Run specific app tests
|
|
python manage.py test api.tests
|
|
python manage.py test scheduler.tests
|
|
|
|
# Run specific test file
|
|
python manage.py test common.tests.test_crud
|
|
|
|
# Run specific test class
|
|
python manage.py test scheduler.tests.test_models.MatchModelTest
|
|
|
|
# Run specific test method
|
|
python manage.py test scheduler.tests.test_models.MatchModelTest.test_create_match
|
|
|
|
# With verbosity
|
|
python manage.py test -v 2
|
|
|
|
# With coverage
|
|
coverage run manage.py test
|
|
coverage report
|
|
coverage html
|
|
```
|
|
|
|
## Common Pitfalls
|
|
|
|
- **Missing `databases = '__all__'`**: Required for multi-database tests
|
|
- **Middleware interference**: Use `@modify_settings` to remove problematic middleware
|
|
- **Session not set**: Set session values before testing session-dependent views
|
|
- **Factory relationships**: Ensure factory SubFactory references match your model structure
|
|
- **Transaction issues**: Use `TransactionTestCase` for tests requiring actual commits
|
|
|
|
## Verification
|
|
|
|
After writing tests:
|
|
|
|
```bash
|
|
# Check test discovery
|
|
python manage.py test --list
|
|
|
|
# Run with failfast
|
|
python manage.py test --failfast
|
|
|
|
# Check coverage
|
|
coverage run --source='scheduler,api,common' manage.py test
|
|
coverage report --fail-under=80
|
|
```
|