Docs
20.5-Code-Quality-Coverage
20.5 Code Quality & Coverage
Overview
Code quality and test coverage are essential metrics for maintaining healthy, maintainable codebases. This section covers static analysis, linting, coverage measurement, and quality gates.
The Quality Triangle
βββββββββββββββββββ
β Code Quality β
β Triangle β
ββββββββββ¬βββββββββ
β
ββββββββββββββββΌβββββββββββββββ
β β β
βΌ βΌ βΌ
ββββββββββββ ββββββββββββ ββββββββββββ
β Static β β Testing β β Runtime β
β Analysis β β Coverage β β Monitor β
ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ
β β β
ββββββββββ΄βββββββββ β ββββββββββ΄βββββββββ
β β’ Linting β β β β’ Error Track β
β β’ Type Check β β β β’ Performance β
β β’ Complexity β β β β’ Usage Metrics β
βββββββββββββββββββ β βββββββββββββββββββ
β
ββββββββββββββ΄βββββββββββββ
β β’ Line Coverage β
β β’ Branch Coverage β
β β’ Function Coverage β
β β’ Statement Coverage β
βββββββββββββββββββββββββββ
Coverage Types
Coverage Metrics Explained
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Coverage Metrics β
βββββββββββββββββ¬ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββ€
β Metric β What It Measures β Example β
βββββββββββββββββΌββββββββββββββββββββββΌβββββββββββββββββββββββββββββββ€
β Line β Lines executed β 80/100 lines = 80% β
β Statement β Statements run β Finer than line (multi-line) β
β Branch β Decision paths β Both if/else tested β
β Function β Functions called β All functions invoked β
β Condition β Boolean conditions β Each && || operand tested β
βββββββββββββββββ΄ββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββ
Coverage Visualization
function calculateDiscount(price, memberLevel) {
let discount = 0; // Line 1: β Covered
if (price > 100) {
// Line 2: Branch A β
if (memberLevel === 'gold') {
// Line 3: Branch B1 β
discount = 0.2; // Line 4: β Covered
} else if (memberLevel === 'silver') {
// Line 5: Branch B2 β
discount = 0.15; // Line 6: β NOT Covered
} else {
// Line 7: Branch B3 β
discount = 0.1; // Line 8: β NOT Covered
}
} // Line 9: Branch A-else β
return price * (1 - discount); // Line 10: β Covered
}
// Test only covers: gold member with price > 100
// Missing: silver, regular members, price <= 100
Coverage Report:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β calculateDiscount.js β
β β
β Statements: ββββββββββββββββββββ 60% β
β Branches: ββββββββββββββββββββ 33% β
β Functions: ββββββββββββββββββββ 100% β
β Lines: ββββββββββββββββββββ 60% β
β β
β Uncovered Lines: 6, 8 β
β Uncovered Branches: 5 β 6, 7 β 8 β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ESLint Configuration
Recommended Setup
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
jest: true,
},
extends: ['eslint:recommended'],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
// Possible Errors
'no-console': 'warn',
'no-debugger': 'error',
'no-duplicate-case': 'error',
'no-empty': 'error',
'no-extra-semi': 'error',
// Best Practices
curly: ['error', 'all'],
eqeqeq: ['error', 'always'],
'no-eval': 'error',
'no-implied-eval': 'error',
'no-return-await': 'error',
// Variables
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'no-use-before-define': 'error',
// Stylistic
indent: ['error', 4],
quotes: ['error', 'single'],
semi: ['error', 'always'],
'comma-dangle': ['error', 'never'],
// ES6
'prefer-const': 'error',
'prefer-arrow-callback': 'error',
'no-var': 'error',
},
};
ESLint Rule Categories
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ESLint Rule Categories β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββ€
β Category β Examples β
ββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββ€
β Possible Errors β no-dupe-args, no-unreachable, valid-typeof β
β Best Practices β eqeqeq, no-eval, no-multi-spaces β
β Variables β no-unused-vars, no-shadow, no-undef β
β Stylistic Issues β indent, quotes, semi, comma-spacing β
β ECMAScript 6 β prefer-const, no-var, arrow-spacing β
β Deprecated β no-native-reassign β no-global-assign β
ββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββ
Prettier Integration
Prettier + ESLint Configuration
// .prettierrc.js
module.exports = {
printWidth: 80,
tabWidth: 4,
useTabs: false,
semi: true,
singleQuote: true,
quoteProps: 'as-needed',
trailingComma: 'none',
bracketSpacing: true,
arrowParens: 'always',
endOfLine: 'lf',
};
// .eslintrc.js - Add Prettier
module.exports = {
extends: [
'eslint:recommended',
'prettier', // Disables conflicting rules
],
plugins: ['prettier'],
rules: {
'prettier/prettier': 'error',
},
};
Jest Coverage Configuration
Setting Up Coverage
// jest.config.js
module.exports = {
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: [
'text', // Console output
'text-summary', // Summary in console
'lcov', // HTML report + lcov.info
'json', // JSON report
'clover', // Clover XML
],
collectCoverageFrom: [
'src/**/*.js',
'!src/**/*.test.js',
'!src/**/*.spec.js',
'!src/**/index.js',
'!src/**/__mocks__/**',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
'./src/critical/**/*.js': {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
coveragePathIgnorePatterns: ['/node_modules/', '/vendor/'],
};
Coverage Report Example
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Coverage Summary β
ββββββββββββββββββββββββ¬βββββββββ¬βββββββββ¬βββββββββ¬βββββββββ¬ββββββββββββββββββ€
β File β % Stmts β % Branch β % Funcs β % Lines β Uncovered β
ββββββββββββββββββββββββΌβββββββββββΌβββββββββββΌββββββββββΌββββββββββΌββββββββββββββ€
β All files β 85.71 β 75.00 β 100.00 β 85.71 β β
β src/ β 85.71 β 75.00 β 100.00 β 85.71 β β
β calculator.js β 100.00 β 100.00 β 100.00 β 100.00 β β
β validator.js β 71.43 β 50.00 β 100.00 β 71.43 β 15,22 β
β utils.js β 80.00 β 66.67 β 100.00 β 80.00 β 28 β
ββββββββββββββββββββββββ΄βββββββββββ΄βββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββββββ
Code Complexity Metrics
Cyclomatic Complexity
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Cyclomatic Complexity β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Complexity = Edges - Nodes + 2 β
β β
β Or simply: β
β Complexity = 1 + (number of decision points) β
β β
β Decision points: if, else if, for, while, &&, ||, ?, catch β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Complexity Score β Risk Level β Recommendation β
ββββββββββββββββββββββΌββββββββββββββββΌββββββββββββββββββββββββββββββββ€
β 1-4 β Low β β Simple, well-testable β
β 5-7 β Moderate β ~ Consider simplifying β
β 8-10 β High β β Needs refactoring β
β 11+ β Very High β β High risk, split function β
ββββββββββββββββββββββ΄ββββββββββββββββ΄ββββββββββββββββββββββββββββββββ
Complexity Example
// Complexity: 7 (High - needs refactoring)
function processOrder(order) {
if (!order) return null; // +1
if (!order.items || order.items.length === 0) {
// +1 +1 (||)
return { error: 'No items' };
}
let total = 0;
for (const item of order.items) {
// +1
if (item.quantity > 0 && item.price > 0) {
// +1 +1 (&&)
total += item.quantity * item.price;
}
}
return { total, itemCount: order.items.length };
}
// Refactored: Split into smaller functions
function validateOrder(order) {
// Complexity: 2
if (!order) return { valid: false, error: 'No order' };
if (!order.items?.length) return { valid: false, error: 'No items' };
return { valid: true };
}
function calculateTotal(items) {
// Complexity: 2
return items.reduce((sum, item) => {
const validItem = item.quantity > 0 && item.price > 0;
return sum + (validItem ? item.quantity * item.price : 0);
}, 0);
}
function processOrderRefactored(order) {
// Complexity: 2
const validation = validateOrder(order);
if (!validation.valid) return { error: validation.error };
return {
total: calculateTotal(order.items),
itemCount: order.items.length,
};
}
Quality Gates
CI/CD Quality Gate Configuration
# .github/workflows/quality.yml
name: Quality Gate
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install Dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Type Check
run: npm run typecheck
- name: Unit Tests
run: npm run test:coverage
- name: Coverage Check
run: |
coverage=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
if (( $(echo "$coverage < 80" | bc -l) )); then
echo "Coverage $coverage% is below 80% threshold"
exit 1
fi
- name: Complexity Check
run: npm run complexity -- --max 10
- name: Security Audit
run: npm audit --production
Quality Gate Thresholds
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Quality Gate Thresholds β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββ€
β Metric β Threshold β
ββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββ€
β Code Coverage β β₯ 80% (lines, branches, functions) β
β Critical Coverage β 100% for payment, security modules β
β Cyclomatic Complexityβ β€ 10 per function β
β Cognitive Complexity β β€ 15 per function β
β Duplicate Code β β€ 3% of codebase β
β Technical Debt Ratio β β€ 5% β
β Security Vulns β 0 high/critical β
β Lint Errors β 0 β
ββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββ
Static Analysis Tools
Tool Comparison
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Static Analysis Tools β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Tool β Purpose β
ββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β ESLint β Code style, potential errors, best practices β
β Prettier β Code formatting, consistent style β
β TypeScript β Type checking, catch type errors at compile time β
β SonarQube β Comprehensive quality (complexity, duplication, security) β
β Husky β Git hooks (pre-commit, pre-push checks) β
β lint-staged β Run linters on staged files only β
β commitlint β Enforce commit message conventions β
β depcheck β Find unused/missing dependencies β
β npm audit β Security vulnerability scanning β
β Snyk β Deep security analysis β
ββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Pre-commit Hooks
Husky + lint-staged Setup
// package.json
{
"scripts": {
"prepare": "husky install",
"lint": "eslint src/**/*.js",
"format": "prettier --write src/**/*.js",
"test": "jest",
"test:coverage": "jest --coverage"
},
"lint-staged": {
"*.js": [
"eslint --fix",
"prettier --write"
],
"*.{json,md}": [
"prettier --write"
]
}
}
// .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
// .husky/pre-push
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run test:coverage
Pre-commit Flow
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Pre-commit Hook Flow β
ββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββ
β git commit -m "" β
βββββββββββββ¬ββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β Husky intercepts commit β
βββββββββββββββββ¬ββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β lint-staged runs on β
β staged files only β
βββββββββββββββββ¬ββββββββββββββββββ
β
βββββββββββββββββββββββΌββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββ βββββββββββββββββ βββββββββββββββββ
β ESLint Fix β β Prettier β β Jest (fast) β
βββββββββ¬ββββββββ βββββββββ¬ββββββββ βββββββββ¬ββββββββ
β β β
βββββββββββββββββββ¬ββ΄ββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββ
βββββββ All Passed? βββββββ
β βββββββββββββββββββββ β
β Yes β No
βΌ βΌ
βββββββββββββββββββββ βββββββββββββββββββββ
β Commit Succeeds β β Commit Blocked β
βββββββββββββββββββββ β Fix Issues First β
βββββββββββββββββββββ
Code Metrics Dashboard
Tracking Quality Over Time
// Simple quality metrics tracker
class QualityDashboard {
constructor() {
this.history = [];
}
recordSnapshot(metrics) {
this.history.push({
timestamp: new Date().toISOString(),
...metrics,
});
}
getTrend(metric) {
const values = this.history.map((h) => h[metric]).filter((v) => v != null);
if (values.length < 2) return 'stable';
const recent = values.slice(-5);
const avg = recent.reduce((a, b) => a + b, 0) / recent.length;
const current = values[values.length - 1];
if (current > avg * 1.05) return 'improving';
if (current < avg * 0.95) return 'declining';
return 'stable';
}
generateReport() {
const latest = this.history[this.history.length - 1];
return {
snapshot: latest,
trends: {
coverage: this.getTrend('coverage'),
complexity: this.getTrend('complexity'),
issues: this.getTrend('issues'),
},
};
}
}
Best Practices
Coverage Best Practices
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Coverage Best Practices β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β β DO: β
β β’ Set meaningful thresholds (80% is a good baseline) β
β β’ Require 100% coverage for critical code paths β
β β’ Focus on branch coverage, not just line coverage β
β β’ Review uncovered code - it might be dead code β
β β’ Use coverage to find untested edge cases β
β β’ Track coverage trends over time β
β β
β β DON'T: β
β β’ Chase 100% coverage blindly (diminishing returns) β
β β’ Write tests just to increase coverage β
β β’ Ignore coverage for "simple" code β
β β’ Exclude files without justification β
β β’ Treat coverage as the only quality metric β
β β’ Measure coverage without running in CI β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Remember: Coverage shows what's tested, not how well it's tested β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Code Quality Checklist
Pre-Commit Checklist:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β‘ Code passes all linting rules β
β β‘ Code is properly formatted β
β β‘ All existing tests pass β
β β‘ New code has appropriate test coverage β
β β‘ No console.log or debugger statements β
β β‘ No TODO comments without issue references β
β β‘ Types are properly defined (if using TypeScript) β
β β‘ Complex functions are documented β
β β‘ No security vulnerabilities introduced β
β β‘ Dependencies are up to date β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Key Takeaways
- β’Coverage is a guide, not a goal - Focus on meaningful tests
- β’Automate quality checks - Use pre-commit hooks and CI/CD
- β’Monitor complexity - Keep functions simple and focused
- β’Use multiple metrics - Coverage, complexity, duplication, security
- β’Track trends - Quality should improve or stay stable
- β’Fail fast - Catch issues before they reach production
- β’Balance strictness - Too strict blocks progress, too loose allows bugs
Files in This Section
- β’
README.md- This documentation - β’
examples.js- Coverage, linting, quality gate examples - β’
exercises.js- Practice implementing quality tools