Dev Logs
/System Design/ Chapter 19: Testing Strategies
Chapters
  • 01Chapter 1: Introduction to System Design
  • 02Chapter 2: Load Balancing and Traffic Distribution
  • 03Chapter 3: Caching Strategies and Implementation
  • 04Chapter 4: Database Design and Scaling
  • 05Chapter 5: API Design and Gateway Patterns
  • 06Chapter 6: Message Queues and Event Processing
  • 07Chapter 7: Microservices Architecture
  • 08Chapter 8: Distributed Systems Fundamentals
  • 09Chapter 9: Observability & Monitoring
  • 10Chapter 10: Security & Authentication
  • 11Chapter 11: Horizontal vs Vertical Scaling
  • 12Chapter 12: Content Delivery & Edge Computing
  • 13Chapter 13: Reliability Patterns
  • 14Chapter 14: Data Modeling & Storage
  • 15Chapter 15: Consistency and Consensus
  • 16Chapter 16: Deployment Strategies
  • 17Chapter 17: Disaster Recovery and Business Continuity
  • 18Chapter 18: Performance Optimization
  • 19Chapter 19: Testing Strategies
    • Overview
    • Testing Pyramid for Distributed Systems
    • Unit Testing
    • Integration Testing
    • Contract Testing
    • Load Testing
    • Chaos Engineering
    • Property-Based Testing
    • Testing Best Practices
    • Test Organization
    • Test Data Management
    • Continuous Testing
    • Common Testing Pitfalls
    • Anti-Patterns to Avoid
  • 20Chapter 20: Real-World System Design
  • 21Chapter 21: System Design Interview Preparation
All chapters

Chapter 19: Testing Strategies

Overview

Testing is a critical aspect of system design that ensures reliability, performance, and correctness at scale. This chapter covers comprehensive testing strategies for distributed systems, from unit tests to chaos engineering.

Testing Pyramid for Distributed Systems

        /\           End-to-End Tests
       /  \          (Few, Expensive)
      /    \
     /      \        Integration Tests
    /        \       (Some, Moderate Cost)
   /          \
  /____________\     Unit Tests
                     (Many, Fast, Cheap)

Unit Testing

Principles:

  • Test individual components in isolation
  • Fast execution (< 100ms per test)
  • No external dependencies
  • High code coverage (80%+)
python
import unittest
from unittest.mock import Mock, patch
from datetime import datetime, timedelta

class UserService:
    def __init__(self, user_repository, email_service, cache):
        self.user_repository = user_repository
        self.email_service = email_service
        self.cache = cache
    
    def create_user(self, username, email, password):
        # Validate input
        if not username or len(username) < 3:
            raise ValueError("Username must be at least 3 characters")
        
        if not self._is_valid_email(email):
            raise ValueError("Invalid email format")
        
        # Check if user exists
        if self.user_repository.find_by_username(username):
            raise ValueError("Username already exists")
        
        if self.user_repository.find_by_email(email):
            raise ValueError("Email already exists")
        
        # Create user
        user = {
            'username': username,
            'email': email,
            'password_hash': self._hash_password(password),
            'created_at': datetime.utcnow(),
            'is_active': True
        }
        
        user_id = self.user_repository.save(user)
        user['id'] = user_id
        
        # Send welcome email
        self.email_service.send_welcome_email(email, username)
        
        # Cache user
        self.cache.set(f"user:{user_id}", user, ttl=3600)
        
        return user
    
    def _is_valid_email(self, email):
        import re
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        return re.match(pattern, email) is not None
    
    def _hash_password(self, password):
        import hashlib
        return hashlib.sha256(password.encode()).hexdigest()

class TestUserService(unittest.TestCase):
    def setUp(self):
        self.user_repository = Mock()
        self.email_service = Mock()
        self.cache = Mock()
        self.user_service = UserService(
            self.user_repository,
            self.email_service,
            self.cache
        )
    
    def test_create_user_success(self):
        # Arrange
        username = "testuser"
        email = "test@example.com"
        password = "password123"
        
        self.user_repository.find_by_username.return_value = None
        self.user_repository.find_by_email.return_value = None
        self.user_repository.save.return_value = "user123"
        
        # Act
        result = self.user_service.create_user(username, email, password)
        
        # Assert
        self.assertEqual(result['username'], username)
        self.assertEqual(result['email'], email)
        self.assertEqual(result['id'], "user123")
        self.assertTrue(result['is_active'])
        
        # Verify interactions
        self.user_repository.find_by_username.assert_called_once_with(username)
        self.user_repository.find_by_email.assert_called_once_with(email)
        self.user_repository.save.assert_called_once()
        self.email_service.send_welcome_email.assert_called_once_with(email, username)
        self.cache.set.assert_called_once()
    
    def test_create_user_invalid_username(self):
        # Test short username
        with self.assertRaises(ValueError) as context:
            self.user_service.create_user("ab", "test@example.com", "password123")
        
        self.assertIn("Username must be at least 3 characters", str(context.exception))
    
    def test_create_user_invalid_email(self):
        with self.assertRaises(ValueError) as context:
            self.user_service.create_user("testuser", "invalid-email", "password123")
        
        self.assertIn("Invalid email format", str(context.exception))
    
    def test_create_user_duplicate_username(self):
        self.user_repository.find_by_username.return_value = {"id": "existing"}
        
        with self.assertRaises(ValueError) as context:
            self.user_service.create_user("testuser", "test@example.com", "password123")
        
        self.assertIn("Username already exists", str(context.exception))
    
    def test_create_user_duplicate_email(self):
        self.user_repository.find_by_username.return_value = None
        self.user_repository.find_by_email.return_value = {"id": "existing"}
        
        with self.assertRaises(ValueError) as context:
            self.user_service.create_user("testuser", "test@example.com", "password123")
        
        self.assertIn("Email already exists", str(context.exception))

if __name__ == '__main__':
    unittest.main()

Integration Testing

Focus Areas:

  • Service-to-service communication
  • Database interactions
  • External API integrations
  • Message queue interactions
python
import pytest
import asyncio
import aiohttp
from testcontainers.postgres import PostgresContainer
from testcontainers.redis import RedisContainer

class IntegrationTestBase:
    @classmethod
    def setup_class(cls):
        # Start test containers
        cls.postgres = PostgresContainer("postgres:13")
        cls.postgres.start()
        
        cls.redis = RedisContainer("redis:6")
        cls.redis.start()
        
        # Setup test database
        cls.db_url = cls.postgres.get_connection_url()
        cls.redis_url = cls.redis.get_connection_url()
    
    @classmethod
    def teardown_class(cls):
        cls.postgres.stop()
        cls.redis.stop()

class TestUserServiceIntegration(IntegrationTestBase):
    def setup_method(self):
        # Initialize services with real dependencies
        self.user_repository = PostgresUserRepository(self.db_url)
        self.email_service = EmailService()
        self.cache = RedisCache(self.redis_url)
        self.user_service = UserService(
            self.user_repository,
            self.email_service,
            self.cache
        )
        
        # Setup test data
        self.user_repository.create_tables()
    
    def teardown_method(self):
        # Clean up test data
        self.user_repository.drop_tables()
        self.cache.flush_all()
    
    def test_user_creation_flow(self):
        # Test complete user creation flow
        username = "integrationtest"
        email = "integration@example.com"
        password = "password123"
        
        # Create user
        user = self.user_service.create_user(username, email, password)
        
        # Verify user in database
        db_user = self.user_repository.find_by_id(user['id'])
        assert db_user is not None
        assert db_user['username'] == username
        assert db_user['email'] == email
        
        # Verify user in cache
        cached_user = self.cache.get(f"user:{user['id']}")
        assert cached_user is not None
        assert cached_user['username'] == username
    
    def test_duplicate_user_handling(self):
        # Create first user
        self.user_service.create_user("testuser", "test@example.com", "password123")
        
        # Try to create duplicate
        with pytest.raises(ValueError, match="Username already exists"):
            self.user_service.create_user("testuser", "other@example.com", "password123")
        
        with pytest.raises(ValueError, match="Email already exists"):
            self.user_service.create_user("otheruser", "test@example.com", "password123")

class TestAPIIntegration:
    @pytest.fixture
    async def client(self):
        # Setup test client
        app = create_app(test_config=True)
        async with aiohttp.ClientSession() as session:
            yield TestClient(app, session)
    
    async def test_user_registration_api(self, client):
        # Test user registration endpoint
        user_data = {
            "username": "apitest",
            "email": "apitest@example.com",
            "password": "password123"
        }
        
        response = await client.post("/api/users", json=user_data)
        
        assert response.status == 201
        data = await response.json()
        assert data['username'] == user_data['username']
        assert data['email'] == user_data['email']
        assert 'password' not in data  # Password should not be returned
    
    async def test_user_login_flow(self, client):
        # Register user
        user_data = {
            "username": "logintest",
            "email": "logintest@example.com",
            "password": "password123"
        }
        
        await client.post("/api/users", json=user_data)
        
        # Login
        login_data = {
            "username": "logintest",
            "password": "password123"
        }
        
        response = await client.post("/api/auth/login", json=login_data)
        
        assert response.status == 200
        data = await response.json()
        assert 'access_token' in data
        assert 'refresh_token' in data

Contract Testing

Consumer-Driven Contracts:

python
import pact
from pact import Consumer, Provider, Like, Term

# Consumer side (User Service)
class TestUserServiceContract:
    def setup_method(self):
        self.pact = Consumer('UserService').has_pact_with(
            Provider('EmailService'),
            host_name='localhost',
            port=1234
        )
        self.pact.start()
    
    def teardown_method(self):
        self.pact.stop()
    
    def test_send_welcome_email(self):
        # Define expected interaction
        expected = {
            'email': 'user@example.com',
            'username': 'testuser',
            'template': 'welcome'
        }
        
        (
            self.pact
            .given('Email service is available')
            .upon_receiving('a request to send welcome email')
            .with_request(
                method='POST',
                path='/api/emails/send',
                headers={'Content-Type': 'application/json'},
                body=expected
            )
            .will_respond_with(
                status=200,
                headers={'Content-Type': 'application/json'},
                body={
                    'message_id': Like('msg_123'),
                    'status': 'sent'
                }
            )
        )
        
        # Test the interaction
        with self.pact:
            email_service = EmailService('http://localhost:1234')
            result = email_service.send_welcome_email(
                'user@example.com',
                'testuser'
            )
            
            assert result['status'] == 'sent'
            assert 'message_id' in result

# Provider side (Email Service)
class TestEmailServiceProvider:
    def test_provider_honors_contract(self):
        # Verify that the Email Service honors the contract
        verifier = pact.Verifier(
            provider='EmailService',
            provider_base_url='http://localhost:8080'
        )
        
        # Run verification
        output, logs = verifier.verify_pacts(
            './pacts/userservice-emailservice.json',
            provider_states_setup_url='http://localhost:8080/setup'
        )
        
        assert output == 0  # Verification passed

Load Testing

Using Locust:

python
from locust import HttpUser, task, between
import random
import string

class UserBehavior(HttpUser):
    wait_time = between(1, 3)  # Wait 1-3 seconds between requests
    
    def on_start(self):
        """Called when a user starts"""
        self.username = self.generate_username()
        self.email = f"{self.username}@example.com"
        self.password = "password123"
        
        # Register user
        self.register()
        
        # Login user
        self.login()
    
    def generate_username(self):
        return ''.join(random.choices(string.ascii_lowercase, k=8))
    
    def register(self):
        response = self.client.post("/api/users", json={
            "username": self.username,
            "email": self.email,
            "password": self.password
        })
        
        if response.status_code == 201:
            self.user_id = response.json()['id']
    
    def login(self):
        response = self.client.post("/api/auth/login", json={
            "username": self.username,
            "password": self.password
        })
        
        if response.status_code == 200:
            self.access_token = response.json()['access_token']
            self.client.headers.update({
                'Authorization': f'Bearer {self.access_token}'
            })
    
    @task(3)
    def view_profile(self):
        """View user profile (most common action)"""
        self.client.get(f"/api/users/{self.user_id}")
    
    @task(2)
    def update_profile(self):
        """Update user profile"""
        self.client.put(f"/api/users/{self.user_id}", json={
            "bio": f"Updated bio at {random.randint(1, 1000)}"
        })
    
    @task(1)
    def search_users(self):
        """Search for users"""
        query = random.choice(['test', 'user', 'admin', 'demo'])
        self.client.get(f"/api/users/search?q={query}")
    
    @task(1)
    def logout(self):
        """Logout user"""
        self.client.post("/api/auth/logout")

class AdminBehavior(HttpUser):
    wait_time = between(5, 10)
    weight = 1  # 1 admin for every 10 regular users
    
    def on_start(self):
        # Admin login
        response = self.client.post("/api/auth/login", json={
            "username": "admin",
            "password": "admin123"
        })
        
        if response.status_code == 200:
            self.access_token = response.json()['access_token']
            self.client.headers.update({
                'Authorization': f'Bearer {self.access_token}'
            })
    
    @task
    def view_dashboard(self):
        self.client.get("/api/admin/dashboard")
    
    @task
    def view_users(self):
        self.client.get("/api/admin/users")
    
    @task
    def view_metrics(self):
        self.client.get("/api/admin/metrics")

# Run with: locust -f load_test.py --host=http://localhost:8000

Chaos Engineering

Chaos Testing Framework:

python
import random
import time
import asyncio
from typing import List, Callable
from abc import ABC, abstractmethod

class ChaosExperiment(ABC):
    def __init__(self, name: str, duration: int):
        self.name = name
        self.duration = duration
        self.is_running = False
    
    @abstractmethod
    async def inject_chaos(self):
        """Inject chaos into the system"""
        pass
    
    @abstractmethod
    async def restore_system(self):
        """Restore system to normal state"""
        pass
    
    async def run(self):
        """Run the chaos experiment"""
        print(f"Starting chaos experiment: {self.name}")
        self.is_running = True
        
        try:
            await self.inject_chaos()
            await asyncio.sleep(self.duration)
        finally:
            await self.restore_system()
            self.is_running = False
            print(f"Chaos experiment completed: {self.name}")

class NetworkLatencyExperiment(ChaosExperiment):
    def __init__(self, target_service: str, latency_ms: int, duration: int):
        super().__init__(f"Network Latency - {target_service}", duration)
        self.target_service = target_service
        self.latency_ms = latency_ms
    
    async def inject_chaos(self):
        # Simulate network latency using traffic control
        import subprocess
        
        # Add latency to network interface
        cmd = f"tc qdisc add dev eth0 root netem delay {self.latency_ms}ms"
        subprocess.run(cmd, shell=True, check=True)
        print(f"Injected {self.latency_ms}ms latency")
    
    async def restore_system(self):
        import subprocess
        
        # Remove network latency
        cmd = "tc qdisc del dev eth0 root"
        subprocess.run(cmd, shell=True, check=False)
        print("Restored network latency")

class ServiceFailureExperiment(ChaosExperiment):
    def __init__(self, service_name: str, failure_rate: float, duration: int):
        super().__init__(f"Service Failure - {service_name}", duration)
        self.service_name = service_name
        self.failure_rate = failure_rate
        self.original_handler = None
    
    async def inject_chaos(self):
        # Monkey patch service to introduce failures
        service = get_service(self.service_name)
        self.original_handler = service.handle_request
        
        def chaotic_handler(*args, **kwargs):
            if random.random() < self.failure_rate:
                raise Exception(f"Chaos: {self.service_name} failure")
            return self.original_handler(*args, **kwargs)
        
        service.handle_request = chaotic_handler
        print(f"Injected {self.failure_rate*100}% failure rate to {self.service_name}")
    
    async def restore_system(self):
        service = get_service(self.service_name)
        service.handle_request = self.original_handler
        print(f"Restored {self.service_name} to normal operation")

class DatabaseConnectionExperiment(ChaosExperiment):
    def __init__(self, database_name: str, duration: int):
        super().__init__(f"Database Connection - {database_name}", duration)
        self.database_name = database_name
    
    async def inject_chaos(self):
        # Simulate database connection issues
        db_pool = get_database_pool(self.database_name)
        
        # Close all connections
        await db_pool.close_all_connections()
        print(f"Closed all connections to {self.database_name}")
    
    async def restore_system(self):
        # Restore database connections
        db_pool = get_database_pool(self.database_name)
        await db_pool.restore_connections()
        print(f"Restored connections to {self.database_name}")

class ChaosRunner:
    def __init__(self):
        self.experiments: List[ChaosExperiment] = []
        self.metrics_collector = MetricsCollector()
    
    def add_experiment(self, experiment: ChaosExperiment):
        self.experiments.append(experiment)
    
    async def run_experiment(self, experiment: ChaosExperiment):
        # Start metrics collection
        self.metrics_collector.start_collection(experiment.name)
        
        try:
            await experiment.run()
        except Exception as e:
            print(f"Experiment {experiment.name} failed: {e}")
        finally:
            # Stop metrics collection and analyze
            metrics = self.metrics_collector.stop_collection()
            self.analyze_results(experiment.name, metrics)
    
    def analyze_results(self, experiment_name: str, metrics: dict):
        print(f"\nAnalyzing results for {experiment_name}:")
        print(f"Average response time: {metrics.get('avg_response_time', 0):.2f}ms")
        print(f"Error rate: {metrics.get('error_rate', 0):.2f}%")
        print(f"Throughput: {metrics.get('throughput', 0):.2f} req/s")
        
        # Check if system remained stable
        if metrics.get('error_rate', 0) > 5:  # 5% error threshold
            print("⚠️  System showed instability during chaos")
        else:
            print("✅ System remained stable during chaos")
    
    async def run_all_experiments(self):
        for experiment in self.experiments:
            await self.run_experiment(experiment)
            await asyncio.sleep(30)  # Wait between experiments

class MetricsCollector:
    def __init__(self):
        self.collecting = False
        self.start_time = None
        self.metrics = {}
    
    def start_collection(self, experiment_name: str):
        self.collecting = True
        self.start_time = time.time()
        self.metrics = {
            'response_times': [],
            'error_count': 0,
            'request_count': 0
        }
    
    def record_request(self, response_time: float, is_error: bool):
        if self.collecting:
            self.metrics['response_times'].append(response_time)
            self.metrics['request_count'] += 1
            if is_error:
                self.metrics['error_count'] += 1
    
    def stop_collection(self) -> dict:
        self.collecting = False
        duration = time.time() - self.start_time
        
        response_times = self.metrics['response_times']
        avg_response_time = sum(response_times) / len(response_times) if response_times else 0
        error_rate = (self.metrics['error_count'] / self.metrics['request_count']) * 100 if self.metrics['request_count'] > 0 else 0
        throughput = self.metrics['request_count'] / duration if duration > 0 else 0
        
        return {
            'avg_response_time': avg_response_time,
            'error_rate': error_rate,
            'throughput': throughput,
            'duration': duration
        }

# Usage example
async def run_chaos_tests():
    runner = ChaosRunner()
    
    # Add experiments
    runner.add_experiment(NetworkLatencyExperiment("user-service", 100, 60))
    runner.add_experiment(ServiceFailureExperiment("email-service", 0.1, 120))
    runner.add_experiment(DatabaseConnectionExperiment("user-db", 90))
    
    # Run all experiments
    await runner.run_all_experiments()

# asyncio.run(run_chaos_tests())

Property-Based Testing

python
from hypothesis import given, strategies as st, assume
import hypothesis.strategies as st

class TestUserServiceProperties:
    @given(
        username=st.text(min_size=3, max_size=50, alphabet=st.characters(whitelist_categories=['Lu', 'Ll', 'Nd'])),
        email=st.emails(),
        password=st.text(min_size=8, max_size=100)
    )
    def test_user_creation_properties(self, username, email, password):
        # Assume valid inputs
        assume(len(username) >= 3)
        assume('@' in email)
        assume(len(password) >= 8)
        
        user_service = UserService(Mock(), Mock(), Mock())
        user_service.user_repository.find_by_username.return_value = None
        user_service.user_repository.find_by_email.return_value = None
        user_service.user_repository.save.return_value = "user123"
        
        # Property: User creation should always succeed with valid inputs
        user = user_service.create_user(username, email, password)
        
        assert user['username'] == username
        assert user['email'] == email
        assert user['is_active'] is True
        assert 'password' not in user  # Password should not be stored in plain text
    
    @given(
        usernames=st.lists(st.text(min_size=3, max_size=20), min_size=2, max_size=10, unique=True)
    )
    def test_unique_username_property(self, usernames):
        user_service = UserService(Mock(), Mock(), Mock())
        created_users = []
        
        for username in usernames:
            email = f"{username}@example.com"
            
            # Mock repository to return existing users
            user_service.user_repository.find_by_username.side_effect = lambda u: next(
                (user for user in created_users if user['username'] == u), None
            )
            user_service.user_repository.find_by_email.return_value = None
            user_service.user_repository.save.return_value = f"user_{len(created_users)}"
            
            user = user_service.create_user(username, email, "password123")
            created_users.append(user)
        
        # Property: All created users should have unique usernames
        usernames_created = [user['username'] for user in created_users]
        assert len(usernames_created) == len(set(usernames_created))

Testing Best Practices

Test Organization

Directory Structure:

tests/
├── unit/
│   ├── services/
│   ├── repositories/
│   └── utils/
├── integration/
│   ├── api/
│   ├── database/
│   └── external/
├── contract/
│   ├── consumer/
│   └── provider/
├── load/
├── chaos/
└── fixtures/
    ├── data/
    └── mocks/

Test Data Management

python
import pytest
import factory
from datetime import datetime

class UserFactory(factory.Factory):
    class Meta:
        model = dict
    
    id = factory.Sequence(lambda n: f"user_{n}")
    username = factory.Sequence(lambda n: f"user_{n}")
    email = factory.LazyAttribute(lambda obj: f"{obj.username}@example.com")
    created_at = factory.LazyFunction(datetime.utcnow)
    is_active = True

class PostFactory(factory.Factory):
    class Meta:
        model = dict
    
    id = factory.Sequence(lambda n: f"post_{n}")
    title = factory.Faker('sentence', nb_words=4)
    content = factory.Faker('text', max_nb_chars=500)
    author_id = factory.SubFactory(UserFactory)
    created_at = factory.LazyFunction(datetime.utcnow)

@pytest.fixture
def sample_user():
    return UserFactory()

@pytest.fixture
def sample_users():
    return UserFactory.build_batch(5)

@pytest.fixture
def sample_post(sample_user):
    return PostFactory(author_id=sample_user['id'])

Continuous Testing

GitHub Actions Workflow:

yaml
name: Test Suite

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: 3.9
    
    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install -r requirements-test.txt
    
    - name: Run unit tests
      run: |
        pytest tests/unit/ --cov=src --cov-report=xml
    
    - name: Upload coverage
      uses: codecov/codecov-action@v1

  integration-tests:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:13
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
      
      redis:
        image: redis:6
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: 3.9
    
    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install -r requirements-test.txt
    
    - name: Run integration tests
      run: |
        pytest tests/integration/
      env:
        DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
        REDIS_URL: redis://localhost:6379

  load-tests:
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: 3.9
    
    - name: Install dependencies
      run: |
        pip install locust
    
    - name: Start application
      run: |
        python app.py &
        sleep 10
    
    - name: Run load tests
      run: |
        locust -f tests/load/locustfile.py --host=http://localhost:8000 --users=50 --spawn-rate=5 --run-time=2m --headless

Common Testing Pitfalls

Anti-Patterns to Avoid

1. Testing Implementation Details:

python
# BAD: Testing internal implementation
def test_user_service_calls_repository_save():
    user_service.create_user("test", "test@example.com", "password")
    user_repository.save.assert_called_once()  # Testing implementation

# GOOD: Testing behavior
def test_user_service_creates_user():
    user = user_service.create_user("test", "test@example.com", "password")
    assert user['username'] == "test"  # Testing behavior

2. Flaky Tests:

python
# BAD: Time-dependent test
def test_user_creation_time():
    user = user_service.create_user("test", "test@example.com", "password")
    assert user['created_at'] == datetime.utcnow()  # Will fail due to timing

# GOOD: Controlled time
def test_user_creation_time():
    with patch('datetime.utcnow') as mock_time:
        mock_time.return_value = datetime(2023, 1, 1)
        user = user_service.create_user("test", "test@example.com", "password")
        assert user['created_at'] == datetime(2023, 1, 1)

3. Over-Mocking:

python
# BAD: Mocking everything
def test_complex_business_logic():
    # 20 lines of mocking setup
    # Test becomes meaningless

# GOOD: Test with real objects when possible
def test_complex_business_logic():
    # Use real objects for business logic
    # Mock only external dependencies

Next Topic: Performance Optimization Previous Topic: Data Modeling & Storage Main Index: DEV LOGS - System Design


PreviousChapter 18: Performance OptimizationNextChapter 20: Real-World System Design

Open source, free forever. Built by iammhador.

Contribute on GitHub