Skip to main content

test-quality-analysis

Detect test smells, overmocking, flaky tests, and coverage issues. Analyze test effectiveness, maintainability, and reliability. Use when reviewing tests or improving test quality.

Aller à l'installation

Informations de source

Dépôt
laurigates/CleanScope
Dernière activité de la source
21 décembre 2025 à 14:28
Langue détectée de SKILL.md
anglais
Étoiles
1
Forks
0

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
created
2025-12-16T00:00:00.000Z
modified
2025-12-16T00:00:00.000Z
reviewed
2025-12-16T00:00:00.000Z
name
Test Quality Analysis
description
Detect test smells, overmocking, flaky tests, and coverage issues. Analyze test effectiveness, maintainability, and reliability. Use when reviewing tests or improving test quality.
allowed-tools
Bash, Read, Edit, Write, Grep, Glob, TodoWrite
# Test Quality Analysis Expert knowledge for analyzing and improving test quality - detecting test smells, overmocking, insufficient coverage, and other testing anti-patterns. ## Core Expertise **Test Quality Dimensions** - **Correctness**: Tests verify the right behavior - **Reliability**: Tests are deterministic and not flaky - **Maintainability**: Tests are easy to understand and modify - **Performance**: Tests run quickly - **Coverage**: Tests cover critical code paths - **Isolation**: Tests don't depend on external state ## Test Smells ### Overmocking **Problem**: Mocking too many dependencies, making tests fragile and disconnected from reality. ```typescript // ❌ BAD: Overmocked test('calculate total', () => { const mockAdd = vi.fn(() => 10) const mockMultiply = vi.fn(() => 20) const mockSubtract = vi.fn(() => 5) // Testing implementation, not behavior const result = calculate(mockAdd, mockMultiply, mockSubtract) expect(result).toBe(15) }) // ✅ GOOD: Mock only external dependencies test('calculate order total', () => { const mockPricingAPI = vi.fn(() => ({ tax: 0.1, shipping: 5 })) const order = { items: [{ price: 10 }, { price: 20 }] } const total = calculateTotal(order, mockPricingAPI) expect(total).toBe(38) // 30 + 3 tax + 5 shipping }) ``` **Detection**: - More than 3-4 mocks in a single test - Mocking internal utilities or pure functions - Mocking data structures or value objects - Complex mock setup that mirrors production code **Fix**: - Mock only I/O boundaries (APIs, databases, filesystem) - Use real implementations for business logic - Extract testable pure functions - Consider integration tests instead ### Fragile Tests **Problem**: Tests break with unrelated code changes. ```typescript // ❌ BAD: Fragile selector test('submits form', async ({ page }) => { await page.locator('.form-container > div:nth-child(2) > button').click() }) // ✅ GOOD: Semantic selector test('submits form', async ({ page }) => { await page.getByRole('button', { name: 'Submit' }).click() }) ``` ```python # ❌ BAD: Tests implementation details def test_user_creation(): user = User() user._internal_id = 123 # Testing private attribute assert user._internal_id == 123 # ✅ GOOD: Tests public interface def test_user_creation(): user = User(id=123) assert user.get_id() == 123 ``` **Detection**: - Tests break when refactoring without changing behavior - Assertions on private methods or attributes - Brittle CSS selectors in E2E tests - Testing implementation details vs. behavior **Fix**: - Test public APIs, not internal implementation - Use semantic selectors (role, label, text) - Follow the "test behavior, not implementation" principle - Avoid testing private methods directly ### Flaky Tests **Problem**: Tests pass or fail non-deterministically. ```typescript // ❌ BAD: Race condition test('loads data', async () => { fetchData() await new Promise(resolve => setTimeout(resolve, 1000)) expect(data).toBeDefined() }) // ✅ GOOD: Proper async handling test('loads data', async () => { const data = await fetchData() expect(data).toBeDefined() }) ``` ```python # ❌ BAD: Time-dependent test def test_expires_in_one_hour(): token = create_token() time.sleep(3601) assert token.is_expired() # ✅ GOOD: Inject time dependency def test_expires_in_one_hour(): now = datetime(2024, 1, 1, 12, 0) future = datetime(2024, 1, 1, 13, 1) token = create_token(now) assert token.is_expired(future) ``` **Detection**: - Test passes locally but fails in CI - Test fails when run in different order - Tests with arbitrary `sleep()` or `setTimeout()` - Timing-dependent assertions **Fix**: - Use proper async/await patterns - Mock time and dates - Avoid arbitrary waits, use explicit waiting - Ensure test isolation - Reset shared state between tests ### Test Duplication **Problem**: Similar test logic repeated across multiple tests. ```typescript // ❌ BAD: Duplicated setup test('user can edit profile', async ({ page }) => { await page.goto('/login') await page.fill('[name=email]', 'user@example.com') await page.fill('[name=password]', 'password') await page.click('button[type=submit]') await page.goto('/profile') // Test logic... }) test('user can view settings', async ({ page }) => { await page.goto('/login') await page.fill('[name=email]', 'user@example.com') await page.fill('[name=password]', 'password') await page.click('button[type=submit]') await page.goto('/settings') // Test logic... }) // ✅ GOOD: Extract to fixture/helper async function loginAsUser(page) { await page.goto('/login') await page.fill('[name=email]', 'user@example.com') await page.fill('[name=password]', 'password') await page.click('button[type=submit]') } test('user can edit profile', async ({ page }) => { await loginAsUser(page) await page.goto('/profile') // Test logic... }) ``` **Detection**: - Copy-pasted test setup code - Similar assertion patterns across tests - Repeated fixture or mock configurations **Fix**: - Extract common setup to `beforeEach()` hooks - Create reusable fixtures or test helpers - Use parameterized tests for similar scenarios - Apply DRY principle to test code ### Slow Tests **Problem**: Tests take too long to run, slowing down feedback loop. ```typescript // ❌ BAD: Unnecessary setup in every test describe('User API', () => { beforeEach(async () => { await database.migrate() // Slow! await seedDatabase() // Slow! }) test('creates user', async () => { const user = await createUser({ name: 'John' }) expect(user.name).toBe('John') }) test('updates user', async () => { const user = await createUser({ name: 'John' }) await updateUser(user.id, { name: 'Jane' }) expect(user.name).toBe('Jane') }) }) // ✅ GOOD: Shared expensive setup describe('User API', () => { beforeAll(async () => { await database.migrate() await seedDatabase() }) beforeEach(async () => { await cleanUserTable() // Fast! }) // Tests... }) ``` **Detection**: - Test suite takes > 10 seconds for unit tests - Unnecessary database migrations in tests - No parallelization of independent tests - Excessive E2E tests for unit-testable logic **Fix**: - Use `beforeAll()` for expensive one-time setup - Mock external dependencies - Run tests in parallel - Push tests down the pyramid (more unit, fewer E2E) - Use in-memory databases or test doubles ### Poor Assertions **Problem**: Weak or missing assertions that don't verify behavior. ```typescript // ❌ BAD: No assertion test('creates user', async () => { await createUser({ name: 'John' }) // No verification! }) // ❌ BAD: Weak assertion test('returns users', async () => { const users = await getUsers() expect(users).toBeDefined() // Too vague! }) // ❌ BAD: Assertion on mock test('calls API', async () => { const mockAPI = vi.fn() await service.fetchData(mockAPI) expect(mockAPI).toHaveBeenCalled() // Testing mock, not behavior }) // ✅ GOOD: Strong, specific assertions test('creates user with correct attributes', async () => { const user = await createUser({ name: 'John', email: 'john@example.com' }) expect(user).toMatchObject({ id: expect.any(Number), name: 'John', email: 'john@example.com', createdAt: expect.any(Date), }) }) ``` **Detection**: - Tests with no assertions - Assertions only on mocks, not outputs - Vague assertions (`toBeDefined()`, `toBeTruthy()`) - Not testing edge cases or error conditions **Fix**: - Assert on actual outputs and side effects - Use specific matchers - Test both happy path and error cases - Verify state changes, not just mock calls ### Insufficient Coverage **Problem**: Critical code paths not tested. ```typescript // Source code function calculateDiscount(price: number, coupon?: string): number { if (coupon === 'SAVE20') return price * 0.8 if (coupon === 'SAVE50') return price * 0.5 return price } // ❌ BAD: Only tests happy path test('applies SAVE20 discount', () => { expect(calculateDiscount(100, 'SAVE20')).toBe(80) }) // ✅ GOOD: Tests all paths describe('calculateDiscount', () => { it('applies SAVE20 discount', () => { expect(calculateDiscount(100, 'SAVE20')).toBe(80) }) it('applies SAVE50 discount', () => { expect(calculateDiscount(100, 'SAVE50')).toBe(50) }) it('returns original price for invalid coupon', () => { expect(calculateDiscount(100, 'INVALID')).toBe(100) }) it('returns original price when no coupon provided', () => { expect(calculateDiscount(100)).toBe(100) }) }) ``` **Detection**: - Coverage below 80% for critical modules - Untested error handling paths - Missing edge case tests - No tests for boundary conditions **Fix**: - Aim for 80%+ coverage on business logic - Test error paths and exceptions - Test boundary values (0, null, max values) - Use mutation testing to find weak tests ## Analysis Tools ### Coverage Analysis (TypeScript/JavaScript) ```bash # Vitest coverage vitest --coverage # View HTML report open coverage/index.html # Check thresholds vitest --coverage --coverage.thresholds.lines=80 ``` ```typescript // vitest.config.ts export default defineConfig({ test: { coverage: { provider: 'v8', reporter: ['text', 'html', 'lcov'], thresholds: { lines: 80, functions: 80, branches: 80, statements: 80, }, exclude: [ 'node_modules/', '**/*.config.ts', '**/*.d.ts', '**/types/**', ], }, }, }) ``` ### Coverage Analysis (Python) ```bash # pytest-cov uv run pytest --cov --cov-report=html # View report open htmlcov/index.html # Show missing lines uv run pytest --cov --cov-report=term-missing # Fail if below threshold uv run pytest --cov --cov-fail-under=80 ``` ```toml # pyproject.toml [tool.coverage.run] source = ["src"] branch = true omit = ["*/tests/*", "*/test_*.py"] [tool.coverage.report] precision = 2 show_missing = true skip_covered = false [tool.coverage.html] directory = "htmlcov" ``` ### Test Performance Analysis ```bash # Vitest: Show slow tests vitest --reporter=verbose # pytest: Show slowest tests uv run pytest --durations=10 # pytest: Profile test execution uv run pytest --profile # Playwright: Trace for performance npx playwright test --trace on ``` ## Best Practices Checklist ### Unit Test Quality - [ ] **Fast**: Tests run in milliseconds - [ ] **Isolated**: No dependencies between tests - [ ] **Repeatable**: Same results every time - [ ] **Self-validating**: Clear pass/fail without manual inspection
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub