# Test Strategies Skill **Description**: Comprehensive testing patterns and strategies for full-stack applications. ## When to Use - Writing new tests - Setting up testing infrastructure - Improving test coverage - Debugging test failures ## Testing Pyramid ``` E2E Tests (10%) ───────────────── Integration Tests (20%) ───────────────────────── Unit Tests (70%) ``` ## Unit Testing ### Python (pytest) ```bash # Run all tests python -m pytest tests/ -v # Run specific test file python -m pytest tests/test_auth.py -v # Run with coverage python -m pytest tests/ --cov=src --cov-report=html # Run failing tests first python -m pytest tests/ --lf # Stop on first failure python -m pytest tests/ -x ``` #### Test Structure ```python # tests/test_auth.py import pytest from src.auth import auth_client class TestAuthClient: def setup_method(self): """Run before each test""" self.client = auth_client.AuthClient() def test_login_success(self): """Test successful login""" result = self.client.login("user@example.com", "password") assert result.success is True assert result.token is not None def test_login_failure(self): """Test login with wrong password""" result = self.client.login("user@example.com", "wrong") assert result.success is False assert "Invalid credentials" in result.error @pytest.mark.asyncio async def test_async_login(self): """Test async login method""" result = await self.client.async_login("user@example.com", "password") assert result.success is True ``` ### TypeScript (Vitest) ```bash # Run all tests pnpm test # Run in watch mode pnpm test --watch # Run with coverage pnpm test --coverage # Run specific test file pnpm test auth.test.ts ``` #### Test Structure ```typescript // src/modules/auth/__tests__/auth.test.ts import { describe, it, expect, beforeEach, vi } from 'vitest'; import { AuthService } from '../service'; describe('AuthService', () => { let service: AuthService; beforeEach(() => { service = new AuthService(); }); it('should validate JWT token', async () => { const token = 'valid.jwt.token'; const result = await service.validateToken(token); expect(result.valid).toBe(true); expect(result.userId).toBe('user123'); }); it('should reject invalid token', async () => { const token = 'invalid.token'; const result = await service.validateToken(token); expect(result.valid).toBe(false); }); }); ``` ## Integration Testing ### API Integration Tests ```python # tests/test_api_integration.py import pytest from fastapi.testclient import TestClient from src.main import app client = TestClient(app) def test_user_flow(): """Test complete user registration and login flow""" # Register user response = client.post("/api/auth/register", json={ "email": "test@example.com", "password": "password123" }) assert response.status_code == 201 # Login response = client.post("/api/auth/login", json={ "email": "test@example.com", "password": "password123" }) assert response.status_code == 200 token = response.json()["token"] # Access protected endpoint response = client.get("/api/user/profile", headers={ "Authorization": f"Bearer {token}" }) assert response.status_code == 200 assert response.json()["email"] == "test@example.com" ``` ### Database Integration Tests ```typescript // src/modules/__tests__/integration.test.ts import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import { CosmosClient } from '@azure/cosmos'; import { UserRepository } from '../users/repository'; describe('UserRepository Integration', () => { let client: CosmosClient; let repo: UserRepository; beforeAll(async () => { client = new CosmosClient(process.env.COSMOS_ENDPOINT!); repo = new UserRepository(client); }); it('should create and retrieve user', async () => { const user = { id: 'test-user', email: 'test@example.com', productId: 'lysnrai', }; await repo.create(user); const retrieved = await repo.getById('test-user'); expect(retrieved).toBeDefined(); expect(retrieved.email).toBe(user.email); }); afterAll(async () => { // Cleanup test data await repo.delete('test-user'); }); }); ``` ## End-to-End Testing ### Playwright (Web Dashboards) ```bash # Install Playwright npm install -D @playwright/test # Install browsers npx playwright install # Run E2E tests npm run test:e2e # Run in headed mode (useful for debugging) npm run test:e2e -- --headed ``` #### Test Structure ```typescript // e2e/login.spec.ts import { test, expect } from '@playwright/test'; test.describe('Authentication', () => { test('user can login', async ({ page }) => { await page.goto('/login'); await page.fill('[data-testid=email]', 'admin@example.com'); await page.fill('[data-testid=password]', 'password'); await page.click('[data-testid=login-button]'); // Should redirect to dashboard await expect(page).toHaveURL('/dashboard'); await expect(page.locator('h1')).toContainText('Welcome'); }); test('shows error for invalid credentials', async ({ page }) => { await page.goto('/login'); await page.fill('[data-testid=email]', 'admin@example.com'); await page.fill('[data-testid=password]', 'wrong'); await page.click('[data-testid=login-button]'); await expect(page.locator('[data-testid=error]')).toContainText('Invalid credentials'); }); }); ``` ### Mobile E2E Tests ```typescript // mobile/e2e/app.test.ts import { device, element, by, expect } from 'detox'; describe('Mobile App', () => { beforeAll(async () => { await device.launchApp(); }); beforeEach(async () => { await device.reloadReactNative(); }); it('should show welcome screen', async () => { await expect(element(by.id('welcome-screen'))).toBeVisible(); }); it('should navigate to settings', async () => { await element(by.id('settings-button')).tap(); await expect(element(by.id('settings-screen'))).toBeVisible(); }); }); ``` ## Test Data Management ### Fixtures (Python) ```python # tests/conftest.py import pytest from src.database import get_db_session @pytest.fixture def db_session(): """Provide a database session for tests""" session = get_db_session() yield session session.rollback() session.close() @pytest.fixture def sample_user(): """Provide a sample user for tests""" return { "id": "test-user-123", "email": "test@example.com", "name": "Test User", "productId": "lysnrai" } ``` ### Mock Data (TypeScript) ```typescript // src/__tests__/mocks/data.ts export const mockUser = { id: 'user-123', email: 'test@example.com', createdAt: new Date().toISOString(), productId: 'lysnrai', }; export const mockSubscription = { id: 'sub-123', userId: 'user-123', status: 'active', planId: 'pro', }; ``` ## Testing Best Practices ### 1. Test Naming ```python # Good: descriptive and clear def test_user_login_with_valid_credentials_returns_token() # Bad: vague def test_login() ``` ### 2. AAA Pattern (Arrange, Act, Assert) ```python def test_discount_applied(): # Arrange cart = ShoppingCart() cart.add_item("book", 10) coupon = Coupon("DISCOUNT10", 0.1) # Act cart.apply_coupon(coupon) # Assert assert cart.total == 9.00 ``` ### 3. Test Isolation ```python # Each test should be independent def test_create_user(): user = User(name="Alice") assert user.name == "Alice" def test_update_user(): user = User(name="Bob") # Fresh instance, not from previous test user.update_name("Charlie") assert user.name == "Charlie" ``` ### 4. Use Test Doubles ```typescript // Mock external dependencies vi.mock('@azure/cosmos', () => ({ CosmosClient: vi.fn().mockImplementation(() => ({ database: vi.fn().mockReturnValue({ container: vi.fn().mockReturnValue({ items: { create: vi.fn().mockResolvedValue({ resource: mockUser }) } }) }) }) })) ``` ## Coverage Requirements ### Coverage Targets | Component | Unit Tests | Integration | E2E | Total Coverage | | ------------------- | ---------- | ----------- | --- | -------------- | | Backend Services | 80% | 40% | 10% | 85% | | Frontend Components | 70% | 30% | 20% | 80% | | Shared Libraries | 90% | 50% | 0% | 90% | | Mobile Apps | 70% | 30% | 30% | 80% | ### Coverage Reports ```bash # Python coverage html open htmlcov/index.html # TypeScript open coverage/lcov-report/index.html ``` ## CI/CD Integration ### GitHub Actions ```yaml name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: [3.12] node-version: [20] steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Setup Node uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install dependencies run: | pip install -e ".[dev]" npm install - name: Run tests run: | python -m pytest tests/ --cov=src npm run test --coverage - name: Upload coverage uses: codecov/codecov-action@v3 ``` ## Debugging Tests ### Python ```bash # Run with debugger python -m pytest tests/test_file.py --pdb # Print output python -m pytest tests/ -v -s # Show local variables on failure python -m pytest tests/ --tb=long ``` ### TypeScript ```bash # Run in debug mode pnpm test --inspect-brk # Run specific test file with debugger pnpm test specific.test.ts --inspect-brk ``` ## Performance Testing ### Load Testing (k6) ```javascript // tests/load/api.js import http from 'k6/http'; import { check } from 'k6'; export let options = { stages: [ { duration: '2m', target: 100 }, { duration: '5m', target: 100 }, { duration: '2m', target: 0 }, ], }; export default function () { let response = http.post('http://localhost:8000/api/auth/login', { email: 'test@example.com', password: 'password', }); check(response, { 'status is 200': r => r.status === 200, 'response time < 500ms': r => r.timings.duration < 500, }); } ``` ## Notes - **Test the behavior, not the implementation** - **Keep tests simple and readable** - **Use descriptive test names** - **Mock external dependencies** - **Run tests in CI/CD** - **Monitor coverage, but don't chase 100%** - **Review and refactor tests regularly** ## Related Skills - [Debug Service](./debug-service.md) - When tests fail - [Production Readiness](./production-readiness.md) - Tests part of readiness - [Mobile Code Quality](./mobile-code-quality.md) - Mobile-specific testing