Code coverage requirements
Code coverage is one of the key metrics for measuring test quality, reflecting the extent to which test cases cover code logic. High coverage typically means less untested code, thereby reducing the risk of potential defects. In frontend development, the requirements and practices for code coverage differ from other domains and need to be analyzed in conjunction with specific scenarios and tools.
Basic Concepts of Code Coverage
Code coverage is primarily divided into the following types:
- Line Coverage: How many lines of code were executed by tests
- Function Coverage: How many functions were called by tests
- Branch Coverage: How many conditional branches were covered by tests
- Statement Coverage: How many statements were executed by tests
// Example function
function calculateDiscount(price, isVIP) {
if (isVIP) { // Branch 1
return price * 0.8; // Statement 1
}
return price * 0.9; // Statement 2
}
In this example:
- Line coverage: 3/4 (ignoring the function declaration line)
- Function coverage: 1/1
- Branch coverage: 1/2 (if only non-VIP cases are tested)
- Statement coverage: 1/2 (if only non-VIP cases are tested)
Characteristics of Frontend Code Coverage
Measuring frontend code coverage presents unique challenges:
- DOM operations and event handling are difficult to fully cover
- Asynchronous code complicates coverage measurement
- UI rendering code is hard to test
- Cross-browser behavior differences
// Example of frontend-specific coverage challenges
async function fetchDataAndRender() {
try {
const response = await fetch('/api/data'); // Async operation
const data = await response.json();
document.getElementById('result').innerHTML = data; // DOM operation
} catch (error) {
console.error('Fetch error:', error); // Error handling
}
}
Coverage Tools and Practices
Mainstream frontend testing tools provide coverage support:
Jest Coverage Configuration
// jest.config.js
module.exports = {
collectCoverage: true,
coverageThreshold: {
global: {
branches: 80,
functions: 85,
lines: 90,
statements: 90
}
}
};
Istanbul/NYC Configuration
// .nycrc
{
"extends": "@istanbuljs/nyc-config-babel",
"all": true,
"check-coverage": true,
"branches": 80,
"lines": 90,
"functions": 85,
"statements": 90
}
Setting Reasonable Coverage Targets
Different project phases and modules should have varying coverage requirements:
- Core business logic: 90%+
- Utility functions: 85%+
- UI components: 70-80%
- Third-party integrations: 50-60%
// Examples of different coverage requirements
// Core business logic - Should achieve high coverage
function processPayment(amount, paymentMethod) {
if (!validatePaymentMethod(paymentMethod)) {
throw new Error('Invalid payment method');
}
return paymentGateway.process(amount, paymentMethod);
}
// UI components - Can accept lower coverage
function FancyButton({ onClick, children }) {
return (
<button className="fancy" onClick={onClick}>
{children}
</button>
);
}
Interpreting and Analyzing Coverage Reports
Coverage reports should be analyzed beyond just numbers:
- Reasons for uncovered code: Intentionally ignored or test gaps
- Low-coverage files: Whether they are critical path code
- Coverage trends: Whether new code meets standards
---------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------------|---------|----------|---------|---------|-------------------
src/ | 92.3 | 87.5 | 90.0 | 92.1 |
utils.js | 100 | 100 | 100 | 100 |
components/ | 85.7 | 75.0 | 83.3 | 85.7 |
Button.js | 100 | 100 | 100 | 100 |
Modal.js | 66.7 | 50 | 66.7 | 66.7 | 12-15,20-22
Specific Strategies to Improve Coverage
1. Completing Test Cases
// Original test
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
// Improved version
describe('sum function', () => {
test('positive numbers', () => {
expect(sum(1, 2)).toBe(3);
});
test('negative numbers', () => {
expect(sum(-1, -2)).toBe(-3);
});
test('decimal numbers', () => {
expect(sum(0.1, 0.2)).toBeCloseTo(0.3);
});
});
2. Handling Edge Cases
// Original function
function divide(a, b) {
return a / b;
}
// Improved version
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
// Corresponding tests
describe('divide', () => {
test('normal division', () => {
expect(divide(6, 3)).toBe(2);
});
test('division by zero', () => {
expect(() => divide(5, 0)).toThrow('Division by zero');
});
});
3. Mocking External Dependencies
// Testing API calls
jest.mock('axios');
test('fetches user data', async () => {
axios.get.mockResolvedValue({ data: { id: 1, name: 'John' } });
const user = await fetchUser(1);
expect(user).toEqual({ id: 1, name: 'John' });
expect(axios.get).toHaveBeenCalledWith('/users/1');
});
The Dialectical Relationship Between Coverage and Code Quality
High coverage does not equal high-quality tests:
- Tests without assertions: Code is executed but behavior is not verified
- Over-mocking: Tests are coupled with implementation details
- Ignoring exception paths: Only testing happy paths
// Example of low-quality high-coverage test
test('updateProfile', () => {
const user = { name: 'Alice' };
updateProfile(user, 'Bob'); // Code executed but no assertions
expect(true).toBe(true); // Meaningless assertion
});
// Improved version
test('updateProfile updates name', () => {
const user = { name: 'Alice' };
const updated = updateProfile(user, 'Bob');
expect(updated.name).toBe('Bob');
expect(updated).not.toBe(user); // Verifying immutability
});
Coverage Assurance in Continuous Integration
Integrate coverage checks in CI workflows:
# GitHub Actions example
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- run: npm test -- --coverage
- uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
coverage_command: npm test -- --coverage
Coverage and the Test Pyramid Relationship
Different test types should focus on different coverage aspects:
- Unit tests: High coverage requirements
- Integration tests: Critical path coverage
- E2E tests: Core business process coverage
// Test pyramid example
describe('Shopping Cart Feature', () => {
// Unit tests - High coverage
describe('calculateTotal()', () => {
test('empty cart', () => { /* ... */ });
test('multiple items', () => { /* ... */ });
test('discount application', () => { /* ... */ });
});
// Integration tests - Key interactions
describe('Add Item to Cart', () => {
test('successful addition', async () => { /* ... */ });
test('insufficient stock', async () => { /* ... */ });
});
// E2E tests - Core workflows
describe('Checkout Process', () => {
test('from product page to payment completion', async () => { /* ... */ });
});
});
Coverage and Code Refactoring
The role of coverage metrics during refactoring:
- Refactoring safety: High coverage provides assurance
- Identifying dead code: Continuously uncovered code
- API compatibility checks: Verifying interface consistency through tests
// Refactoring example - Guided by coverage
// Old implementation
function formatName(first, last) {
return last + ', ' + first;
}
// New implementation
function formatName(first, last, options = {}) {
const { reverse = true } = options;
return reverse
? `${last}, ${first}`
: `${first} ${last}`;
}
// Existing tests continue to pass
test('formatName returns last, first', () => {
expect(formatName('John', 'Doe')).toBe('Doe, John');
});
// New test
test('formatName with reverse false', () => {
expect(formatName('John', 'Doe', { reverse: false })).toBe('John Doe');
});
Handling Coverage in Frontend-Specific Scenarios
1. CSS-in-JS Coverage
// styled-components test example
import styled from 'styled-components';
const Button = styled.button`
color: ${props => props.primary ? 'white' : 'black'};
background: ${props => props.primary ? 'blue' : 'gray'};
`;
test('Button styles', () => {
const primaryButton = mount(<Button primary />);
const normalButton = mount(<Button />);
expect(primaryButton).toHaveStyleRule('color', 'white');
expect(normalButton).toHaveStyleRule('color', 'black');
});
2. Component Lifecycle Coverage
// React component lifecycle testing
class Timer extends React.Component {
state = { count: 0 };
componentDidMount() {
this.interval = setInterval(() => {
this.setState(prev => ({ count: prev.count + 1 }));
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return <div>{this.state.count}</div>;
}
}
// Tests
test('Timer lifecycle', () => {
jest.useFakeTimers();
const wrapper = mount(<Timer />);
// componentDidMount
expect(wrapper.text()).toBe('0');
// interval
jest.advanceTimersByTime(1000);
expect(wrapper.text()).toBe('1');
// componentWillUnmount
wrapper.unmount();
expect(clearInterval).toHaveBeenCalled();
});
Balancing Coverage and Performance Testing
Coverage measurement itself has performance overhead; trade-offs are needed:
- Development environment: Comprehensive measurement
- CI environment: Key metrics
- Production builds: Remove measurement code
// Conditionally enabling coverage
if (process.env.NODE_ENV === 'test') {
const { createCoverageMap } = require('istanbul-lib-coverage');
global.__coverage__ = createCoverageMap();
}
// Excluding from production builds
// webpack.config.js
module.exports = (env) => ({
// ...
plugins: env === 'production' ? [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
] : []
});
Coverage Standards in Team Collaboration
Establish unified team coverage standards:
- PR merge requirements: No lower than existing coverage
- New code requirements: Meet team standards
- Exemption mechanism: Special scenario explanations
# Example of team coverage standards
## Frontend Code Coverage Requirements
1. All utility classes and business logic must achieve:
- Line coverage ≥90%
- Branch coverage ≥80%
2. React/Vue components must achieve:
- Line coverage ≥75%
- Full coverage of interaction logic branches
3. Special exemptions:
- Third-party integration code
- Experimental features
- Detailed explanations required
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn