| name | Testing Anti-Patterns |
| description | This skill should be used when encountering "flaky tests", "test maintenance issues", "slow test suites", "brittle tests", "test code smells", "test debugging problems", or when tests are hard to understand, maintain, or debug. |
| version | 1.0.0 |
Testing Anti-Patterns: Prevention and Detection
Overview
This skill identifies and prevents common testing anti-patterns that make test suites unreliable, slow, and difficult to maintain. It provides detection strategies and refactoring solutions for healthier test codebases.
When to Use This Skill
Use this skill when encountering:
- Flaky tests that pass/fail inconsistently
- Slow test suites that take too long to run
- Brittle tests that break on minor code changes
- Hard-to-debug test failures
- Test maintenance nightmares
- Test code duplication and complexity
- Poor test organization and naming
Common Anti-Patterns and Solutions
1. The Mystery Guest Anti-Pattern
Problem: Tests depend on external data that's not visible in the test.
def test_user_login():
"""What user data exists? What are the credentials?"""
user = User.objects.get(email="test@example.com")
response = client.post('/login', {
'email': user.email,
'password': 'secret123'
})
assert response.status_code == 200
def test_user_login():
"""Clear test with explicit data creation."""
user = User.objects.create_user(
email="test@example.com",
password="secret123",
first_name="Test",
last_name="User"
)
response = client.post('/login', {
'email': user.email,
'password': 'secret123'
})
assert response.status_code == 200
assert response.json()['user_id'] == user.id
2. The Slow Poke Anti-Pattern
Problem: Tests take unnecessarily long to run.
import time
def test_api_response():
"""This test is unnecessarily slow."""
time.sleep(5)
response = api_client.get('/slow-endpoint')
time.sleep(2)
assert response.status_code == 200
def test_api_response_fast():
"""Fast test with mocked delays."""
with patch('slow_service.time_consuming_operation') as mock_op:
mock_op.return_value = "mocked_result"
response = api_client.get('/fast-endpoint')
assert response.status_code == 200
mock_op.assert_called_once()
3. The Eager Test Anti-Pattern
Problem: One test tries to verify too many things.
def test_user_management_everything():
"""This test does too much."""
user = create_user("test@example.com")
assert user.id is not None
user.update(first_name="Updated")
assert user.first_name == "Updated"
assign_permission(user, "read")
assert user.has_permission("read")
post = create_post(user, "Test post")
assert post.author == user
user.delete()
assert User.objects.filter(id=user.id).count() == 0
def test_user_creation():
"""Test only user creation."""
user = create_user("test@example.com")
assert user.id is not None
assert user.email == "test@example.com"
def test_user_update():
"""Test only user updates."""
user = create_user("test@example.com")
user.update(first_name="Updated")
user.first_name ==
():
user = create_user()
assign_permission(user, )
user.has_permission()
4. The Fragile Fixture Anti-Pattern
Problem: Fixtures that break easily and affect multiple tests.
@pytest.fixture(scope="session")
def database_with_data():
"""This fixture is fragile and affects all tests."""
db.create_all()
user1 = User.objects.create(email="user1@test.com")
user2 = User.objects.create(email="user2@test.com")
post1 = Post.objects.create(author=user1, title="Post 1")
post2 = Post.objects.create(author=user2, title="Post 2")
yield
db.drop_all()
@pytest.fixture
def clean_database():
"""Clean database for each test."""
db.create_all()
yield
db.drop_all()
@pytest.fixture
def sample_user(clean_database):
"""Create a fresh user for each test."""
return User.objects.create(
email="test@example.com",
first_name="Test",
last_name="User"
)
@pytest.fixture
def sample_post(sample_user):
"""Create a fresh post for each test."""
return Post.objects.create(
author=sample_user,
title="Test Post",
content="Test content"
)
5. The Assertion Roulette Anti-Pattern
Problem: Multiple assertions without clear failure messages.
def test_user_api_response():
"""Which assertion failed? Who knows!"""
response = api_client.get('/users/1')
data = response.json()
assert response.status_code == 200
assert data['id'] == 1
assert data['email'] == 'test@example.com'
assert data['first_name'] == 'Test'
assert data['last_name'] == 'User'
assert data['is_active'] is True
assert data['created_at'] is not None
def test_user_api_response_clear():
"""Each assertion has a clear purpose and message."""
response = api_client.get('/users/1')
assert response.status_code == 200, f"API request failed: {response.text}"
data = response.json()
assert data['id'] == 1, f"Wrong user ID: expected 1, got {data.get('id')}"
assert data['email'] == 'test@example.com',
data[] == ,
data[] == ,
data[] ,
assert_user_data_valid(data, expected_id=, expected_email=)
():
errors = []
data.get() != expected_id:
errors.append()
data.get() != expected_email:
errors.append()
data.get():
errors.append()
errors:
pytest.fail( + .join(errors))
6. The Test Code Duplication Anti-Pattern
Problem: Copy-paste test code that's hard to maintain.
def test_admin_can_create_post():
admin = User.objects.create(email="admin@test.com", role="admin")
auth_token = generate_token(admin)
headers = {"Authorization": f"Bearer {auth_token}"}
response = client.post('/posts', {
'title': 'Admin Post',
'content': 'Admin content'
}, headers=headers)
assert response.status_code == 201
def test_admin_can_update_post():
admin = User.objects.create(email="admin@test.com", role="admin")
auth_token = generate_token(admin)
headers = {"Authorization": f"Bearer {auth_token}"}
post = Post.objects.create(title="Test", content="Test", author=admin)
response = client.put(f'/posts/{post.id}', {
'title': 'Updated Post',
'content': 'Updated content'
}, headers=headers)
assert response.status_code == 200
def test_admin_can_delete_post():
admin = User.objects.create(email="admin@test.com", role="admin")
auth_token = generate_token(admin)
headers = {"Authorization": f"Bearer {auth_token}"}
post = Post.objects.create(title="Test", content="Test", author=admin)
response = client.delete(, headers=headers)
response.status_code ==
:
():
User.objects.create(email=, role=)
():
token = generate_token(admin_user)
{: }
():
Post.objects.create(
title=,
content=,
author=admin_user
)
():
response = client.post(, {
: ,
:
}, headers=auth_headers)
response.status_code ==
():
response = client.put(, {
: ,
:
}, headers=auth_headers)
response.status_code ==
():
response = client.delete(, headers=auth_headers)
response.status_code ==
7. The Secret Dependency Anti-Pattern
Problem: Tests that depend on execution order or hidden state.
class TestUserWorkflow:
def test_01_create_user(self):
"""Must run first!"""
self.user = User.objects.create(email="test@example.com")
assert self.user.id is not None
def test_02_update_user(self):
"""Depends on test_01_create_user!"""
self.user.update(first_name="Updated")
assert self.user.first_name == "Updated"
def test_03_delete_user(self):
"""Depends on previous tests!"""
self.user.delete()
assert User.objects.filter(id=self.user.id).count() == 0
class TestUserWorkflow:
def test_user_creation(self):
"""Independent test for user creation."""
user = User.objects.create(email="test@example.com")
assert user.id
user.email ==
():
user = User.objects.create(email=)
user.update(first_name=)
user.first_name ==
():
user = User.objects.create(email=)
user_id = user.
user.delete()
User.objects.(=user_id).count() ==
Anti-Pattern Detection Tools
1. Test Smell Detector
"""
Tool to detect common test anti-patterns in the codebase.
"""
import ast
import os
from pathlib import Path
from typing import List, Dict, Any
from dataclasses import dataclass
@dataclass
class TestSmell:
"""Represents a detected test anti-pattern."""
file_path: str
line_number: int
smell_type: str
description: str
severity: str
suggestion: str
class TestSmellDetector:
"""Detects common anti-patterns in test files."""
def __init__(self):
self.smells: List[TestSmell] = []
def analyze_test_file(self, file_path: Path) -> List[TestSmell]:
"""Analyze a test file for anti-patterns."""
self.smells = []
with open(file_path, 'r') as f:
content = f.read()
tree = ast.parse(content)
self._analyze_ast(tree, str(file_path))
return self.smells
def ():
node ast.walk(tree):
._check_long_test_methods(node, file_path)
._check_mystery_guest(node, file_path)
._check_assertion_roulette(node, file_path)
._check_slow_poke(node, file_path)
._check_fragile_fixture(node, file_path)
():
(node, ast.FunctionDef) node.name.startswith():
loc = ([n n ast.walk(node) (n, ast.stmt)])
loc > :
.smells.append(TestSmell(
file_path=file_path,
line_number=node.lineno,
smell_type=,
description=,
severity=,
suggestion=
))
():
(node, ast.Call):
((node.func, )
node.func.attr [, , ]
(node.func.value, )
node.func.value.attr == ):
parent = node
parent ((parent, ast.FunctionDef)
parent.name.startswith()):
parent = (parent, , )
parent:
.smells.append(TestSmell(
file_path=file_path,
line_number=node.lineno,
smell_type=,
description=,
severity=,
suggestion=
))
():
(node, ast.FunctionDef) node.name.startswith():
assert_count = ([n n ast.walk(node)
(n, ast.Assert)])
assert_count > :
.smells.append(TestSmell(
file_path=file_path,
line_number=node.lineno,
smell_type=,
description=,
severity=,
suggestion=
))
():
(node, ast.Call):
((node.func, ) node.func.attr ==
(node.func, ast.Name) node.func. == ):
.smells.append(TestSmell(
file_path=file_path,
line_number=node.lineno,
smell_type=,
description=,
severity=,
suggestion=
))
():
((node, ast.FunctionDef)
(d. == d node.decorator_list
(d, ))):
decorator node.decorator_list:
((decorator, ast.Call)
(decorator.func, )
decorator.func.attr == ):
keyword decorator.keywords:
(keyword.arg ==
(keyword.value, ast.Str)
keyword.value.s == ):
.smells.append(TestSmell(
file_path=file_path,
line_number=node.lineno,
smell_type=,
description=,
severity=,
suggestion=
))
() -> :
all_smells = []
test_file test_directory.rglob():
smells = .analyze_test_file(test_file)
all_smells.extend(smells)
smell_groups = {}
smell all_smells:
smell_type = smell.smell_type
smell_type smell_groups:
smell_groups[smell_type] = []
smell_groups[smell_type].append(smell)
report =
report +=
smell_type, smells smell_groups.items():
report +=
smell smells:
report +=
report +=
report
():
detector = TestSmellDetector()
test_dir = Path()
report = detector.generate_report(test_dir)
(, ) f:
f.write(report)
()
2. Test Performance Analyzer
"""
Tool to analyze test suite performance and identify slow tests.
"""
import pytest
import time
import json
from pathlib import Path
from typing import Dict, List
from dataclasses import dataclass, asdict
@dataclass
class TestMetrics:
"""Metrics for a single test."""
name: str
duration: float
status: str
file_path: str
line_number: int
class TestPerformanceAnalyzer:
"""Analyzes test suite performance."""
def __init__(self):
self.test_metrics: List[TestMetrics] = []
def pytest_runtest_protocol(self, item, nextitem):
"""Pytest hook to collect test metrics."""
start_time = time.time()
result = yield
end_time = time.time()
duration = end_time - start_time
metrics = TestMetrics(
name=item.name,
duration=duration,
status=result.outcome if hasattr(result, 'outcome') else 'UNKNOWN',
file_path=str(item.fspath),
line_number=item.function.__code__.co_firstlineno
)
.test_metrics.append(metrics)
() -> :
.test_metrics:
{: }
total_duration = (m.duration m .test_metrics)
avg_duration = total_duration / (.test_metrics)
slow_threshold = avg_duration *
slow_tests = [m m .test_metrics m.duration > slow_threshold]
by_file = {}
metric .test_metrics:
file_path = metric.file_path
file_path by_file:
by_file[file_path] = []
by_file[file_path].append(metric)
file_durations = {
path: (m.duration m metrics)
path, metrics by_file.items()
}
slowest_files = (file_durations.items(), key= x: x[], reverse=)[:]
{
: {
: (.test_metrics),
: (total_duration, ),
: (avg_duration, ),
: (slow_tests),
: (slow_threshold, )
},
: [
{
: m.name,
: (m.duration, ),
: m.file_path,
: m.line_number
}
m (slow_tests, key= x: x.duration, reverse=)[:]
],
: [
{
: path,
: (duration, ),
: (by_file[path])
}
path, duration slowest_files
]
}
():
data = {
: [asdict(m) m .test_metrics],
: .generate_performance_report()
}
(output_path, ) f:
json.dump(data, f, indent=)
():
config.analyzer = TestPerformanceAnalyzer()
config.pluginmanager.register(config.analyzer, )
():
(config, ):
config.analyzer.save_metrics(Path())
()
Project-Specific Patterns for EnterpriseHub
For GHL Real Estate AI Components
class TestStreamlitComponents:
"""Test patterns for Streamlit components."""
@pytest.fixture
def mock_streamlit_session(self):
"""Mock Streamlit session state."""
with patch('streamlit.session_state') as mock_session:
mock_session._state = {}
yield mock_session
def test_property_matcher_component(self, mock_streamlit_session):
"""Test property matcher with proper mocking."""
from components.property_matcher_ai import PropertyMatcherComponent
component = PropertyMatcherComponent()
test_preferences = {
"budget_max": 500000,
"bedrooms": 3,
"location": "Rancho Cucamonga, CA"
}
with patch('streamlit.selectbox') as mock_selectbox:
mock_selectbox.return_value = "Rancho Cucamonga, CA"
result = component.render_preferences_form()
assert result is not None
mock_selectbox.assert_called_once()
def test_ai_training_sandbox_isolation(self):
"""Test AI training with proper isolation."""
components.ai_training_sandbox AITrainingSandbox
patch() mock_ai_service:
mock_ai_service.return_value.train_model.return_value = {
: ,
:
}
sandbox = AITrainingSandbox()
result = sandbox.train_with_sample_data()
result[] >
mock_ai_service.return_value.train_model.assert_called_once()
Best Practices Summary
- Explicit Test Data: Create all test data explicitly within tests
- Independent Tests: Each test should run independently
- Clear Assertions: Use descriptive assertion messages
- Fast Tests: Mock external dependencies and avoid unnecessary delays
- Focused Tests: Test one concept per test method
- Clean Fixtures: Use function-scoped fixtures when possible
- DRY Principles: Extract common test code into reusable helpers
Integration with Other Skills
This skill enhances:
- test-driven-development: By preventing common TDD pitfalls
- verification-before-completion: By ensuring test quality before completion
- systematic-debugging: By making test failures easier to debug
Use the detection tools regularly to maintain test suite health and prevent anti-pattern accumulation.