-
Notifications
You must be signed in to change notification settings - Fork 2
DjangoPerformanceAPITestCase
The production test case for documenting and enforcing performance requirements in Django applications. This class provides manual control with specific assertions for CI/CD pipelines.
DjangoPerformanceAPITestCase is designed for the documentation phase of performance testing. It provides explicit performance assertions that document requirements and fail tests when standards aren't met.
- ✅ Explicit performance assertions for CI/CD
- 📊 Manual monitoring control
- 🎯 Specific threshold enforcement
- 📈 Performance tracking and reporting
- 🔧 Context managers for monitoring
- 📝 Production-ready test documentation
from django_mercury import DjangoPerformanceAPITestCase
from django_mercury import monitor_django_view
class MyPerformanceTests(DjangoPerformanceAPITestCase):
def test_user_list_performance(self):
with monitor_django_view("UserList") as monitor:
response = self.client.get('/api/users/')
# Explicit assertions document requirements
self.assertResponseTimeLess(monitor, 100) # Must respond in 100ms
self.assertQueriesLess(monitor, 5) # Maximum 5 queries
self.assertNoN1Queries(monitor) # No N+1 queries allowedComprehensive performance assertion with multiple criteria.
def assertPerformance(
self,
monitor: EnhancedPerformanceMonitor,
max_response_time: Optional[float] = None,
max_memory_mb: Optional[float] = None,
max_queries: Optional[int] = None,
min_cache_hit_ratio: Optional[float] = None,
msg: Optional[str] = None
) -> NoneExample:
def test_comprehensive_performance(self):
with monitor_django_view("ComplexOperation") as monitor:
response = self.client.post('/api/complex/')
self.assertPerformance(
monitor,
max_response_time=200, # 200ms max
max_memory_mb=50, # 50MB max memory
max_queries=10, # 10 queries max
min_cache_hit_ratio=0.8, # 80% cache hits minimum
msg="Complex operation performance requirements"
)Assert response time is under threshold.
def assertResponseTimeLess(
self,
metrics_or_monitor: Union[EnhancedPerformanceMonitor, EnhancedPerformanceMetrics_Python],
milliseconds: float,
msg: Optional[str] = None
) -> NoneExample:
def test_fast_endpoint(self):
metrics = self.run_comprehensive_analysis(
"FastEndpoint",
lambda: self.client.get('/api/fast/')
)
self.assertResponseTimeLess(metrics, 50, "Should be very fast")Assert memory usage is under threshold.
def assertMemoryLess(
self,
metrics_or_monitor: Union[EnhancedPerformanceMonitor, EnhancedPerformanceMetrics_Python],
megabytes: float,
msg: Optional[str] = None
) -> NoneExample:
def test_memory_efficient(self):
with monitor_django_view("DataProcessing") as monitor:
response = self.client.post('/api/process/')
self.assertMemoryLess(monitor, 100, "Should not exceed 100MB")Assert database query count is under threshold.
def assertQueriesLess(
self,
metrics_or_monitor: Union[EnhancedPerformanceMonitor, EnhancedPerformanceMetrics_Python],
count: int,
msg: Optional[str] = None
) -> NoneExample:
def test_optimized_queries(self):
with monitor_django_view("UserDetail") as monitor:
response = self.client.get('/api/users/1/')
self.assertQueriesLess(monitor, 3, "Should use select_related")Assert cache hit ratio meets minimum.
def assertCacheHitRatioGreater(
self,
metrics_or_monitor: Union[EnhancedPerformanceMonitor, EnhancedPerformanceMetrics_Python],
ratio: float,
msg: Optional[str] = None
) -> NoneExample:
def test_cache_effectiveness(self):
with monitor_django_view("CachedView") as monitor:
for _ in range(10):
self.client.get('/api/cached/')
self.assertCacheHitRatioGreater(monitor, 0.9, "90% cache hit required")Assert no N+1 query patterns exist.
def assertNoN1Queries(
self,
metrics_or_monitor: Union[EnhancedPerformanceMonitor, EnhancedPerformanceMetrics_Python],
msg: Optional[str] = None
) -> NoneExample:
def test_no_n_plus_one(self):
with monitor_django_view("UserListWithProfiles") as monitor:
response = self.client.get('/api/users/?include=profile')
self.assertNoN1Queries(monitor, "Must use select_related")Context manager for monitoring Django views.
def monitor_django_view(
self,
operation_name: str,
auto_report: bool = False
) -> EnhancedPerformanceMonitorExample:
def test_view_performance(self):
monitor = self.monitor_django_view("CreateUser", auto_report=True)
with monitor:
response = self.client.post('/api/users/', data={...})
# Auto-report prints performance summary
self.assertResponseTimeLess(monitor, 200)Context manager for monitoring serializer performance.
def monitor_serializer(
self,
serializer_name: str,
auto_report: bool = False
) -> EnhancedPerformanceMonitorExample:
def test_serializer_performance(self):
from myapp.serializers import UserSerializer
monitor = self.monitor_serializer("UserSerializer")
with monitor:
serializer = UserSerializer(users, many=True)
data = serializer.data
self.assertResponseTimeLess(monitor, 50)Context manager for monitoring model operations.
def monitor_django_model(
self,
model_name: str,
auto_report: bool = False
) -> EnhancedPerformanceMonitorExample:
def test_model_performance(self):
monitor = self.monitor_django_model("User.bulk_create")
with monitor:
User.objects.bulk_create([
User(username=f"user_{i}") for i in range(1000)
])
self.assertResponseTimeLess(monitor, 1000)
self.assertQueriesLess(monitor, 2)Run detailed performance analysis on a test function.
def run_comprehensive_analysis(
self,
operation_name: str,
test_function: callable,
operation_type: str = "general",
expect_response_under: Optional[float] = None,
expect_memory_under: Optional[float] = None,
expect_queries_under: Optional[int] = None,
print_analysis: bool = True,
show_scoring: bool = True,
**kwargs
) -> Optional[EnhancedPerformanceMetrics_Python]Example:
def test_comprehensive_analysis(self):
metrics = self.run_comprehensive_analysis(
"ComplexSearch",
lambda: self.client.get('/api/search?q=test'),
operation_type="search_view",
expect_response_under=300,
expect_queries_under=10,
print_analysis=True, # Show detailed analysis
show_scoring=True, # Show performance grade
auto_detect_n_plus_one=True
)
# Metrics contains detailed performance data
self.assertGreater(metrics.performance_score.total_score, 60)Display a formatted performance dashboard.
def print_performance_dashboard(
self,
metrics: EnhancedPerformanceMetrics_Python,
title: str = "Performance Dashboard"
) -> NoneExample:
def test_with_dashboard(self):
metrics = self.run_comprehensive_analysis(
"Dashboard",
lambda: self.client.get('/api/dashboard/')
)
self.print_performance_dashboard(
metrics,
title="Dashboard API Performance"
)# Basic usage
with monitor_django_view("OperationName") as monitor:
# Your code here
response = self.client.get('/api/endpoint/')
# Access metrics after context
print(f"Response time: {monitor.metrics.response_time}ms")
print(f"Query count: {monitor.metrics.query_count}")def test_nested_operations(self):
with monitor_django_view("ParentOperation") as parent:
response = self.client.get('/api/parent/')
with monitor_django_view("ChildOperation") as child:
self.client.get('/api/child/')
self.assertResponseTimeLess(child, 50)
self.assertResponseTimeLess(parent, 150)Specify operation types for better analysis:
def test_with_operation_type(self):
metrics = self.run_comprehensive_analysis(
"UserList",
lambda: self.client.get('/api/users/'),
operation_type="list_view" # Adjusts expectations
)-
general- Default, balanced thresholds -
list_view- Multiple items, allows more queries -
detail_view- Single item, expects fewer queries -
create_view- Creation, allows validation queries -
update_view- Updates, moderate query count -
delete_view- Deletion, allows cascade queries -
search_view- Search/filter, allows complex queries
def test_simple_performance(self):
with monitor_django_view("Simple") as monitor:
response = self.client.get('/api/simple/')
self.assertResponseTimeLess(monitor, 100)
self.assertQueriesLess(monitor, 5)def test_comprehensive(self):
metrics = self.run_comprehensive_analysis(
"Comprehensive",
lambda: self.client.post('/api/complex/', data={}),
expect_response_under=200,
expect_memory_under=100,
expect_queries_under=20
)
# Additional assertions
self.assertNoN1Queries(metrics)
self.assertGreater(metrics.performance_score.total_score, 70)def test_batch_operations(self):
operations = [
("List", lambda: self.client.get('/api/items/'), 100, 10),
("Create", lambda: self.client.post('/api/items/'), 200, 15),
("Delete", lambda: self.client.delete('/api/items/1/'), 150, 20),
]
for name, operation, max_time, max_queries in operations:
with self.subTest(operation=name):
with monitor_django_view(name) as monitor:
operation()
self.assertResponseTimeLess(monitor, max_time)
self.assertQueriesLess(monitor, max_queries)- name: Run Performance Tests
run: |
python manage.py test tests.performance \
--parallel \
--verbosity=2
env:
DJANGO_MERCURY_ENABLED: "true"import pytest
from django_mercury import DjangoPerformanceAPITestCase
@pytest.mark.performance
class TestAPIPerformance(DjangoPerformanceAPITestCase):
def test_critical_endpoint(self):
with monitor_django_view("Critical") as monitor:
response = self.client.get('/api/critical/')
self.assertResponseTimeLess(monitor, 50)
self.assertQueriesLess(monitor, 3)def test_sla_requirements(self):
"""API must respond within 100ms per SLA agreement."""
with monitor_django_view("SLA") as monitor:
response = self.client.get('/api/sla-endpoint/')
self.assertResponseTimeLess(monitor, 100, "SLA requirement")with monitor_django_view("UserListWithFullProfiles") as monitor:
# Clear operation name helps debugging# Consider actual requirements
self.assertResponseTimeLess(monitor, 200) # User-facing
self.assertResponseTimeLess(monitor, 5000) # Background taskdef test_concurrent_requests(self):
"""Test performance under concurrent load."""
from concurrent.futures import ThreadPoolExecutor
def make_request():
return self.client.get('/api/concurrent/')
with monitor_django_view("ConcurrentLoad") as monitor:
with ThreadPoolExecutor(max_workers=10) as executor:
futures = [executor.submit(make_request) for _ in range(10)]
self.assertResponseTimeLess(monitor, 1000)class InvestigationTests(DjangoMercuryAPITestCase):
def test_discover_issues(self):
response = self.client.get('/api/users/')
# Mercury discovers issues automaticallyclass ProductionTests(DjangoPerformanceAPITestCase):
def test_enforce_standards(self):
with monitor_django_view("UserList") as monitor:
response = self.client.get('/api/users/')
# Explicit assertions based on discovered requirements
self.assertResponseTimeLess(monitor, 100)
self.assertQueriesLess(monitor, 5)
self.assertNoN1Queries(monitor)Performance assertions provide clear error messages:
AssertionError: Response time 150.5ms is not less than 100ms
AssertionError: Query count 25 exceeds maximum 10
AssertionError: N+1 query pattern detected: 50 similar queries found
Fix: Use select_related('profile') or prefetch_related('tags')
AssertionError: Cache hit ratio 0.45 is not greater than 0.8
- DjangoMercuryAPITestCase - Investigation test case
- EnhancedPerformanceMonitor - Monitor class details
- Performance Metrics - Understanding metrics
- Workflow Best Practices - Two-phase workflow