Flutter 测试覆盖率详解
简介
测试覆盖率是衡量测试完整性的重要指标。本文介绍如何在 Flutter 中统计和分析测试覆盖率。
基本配置
添加依赖
yaml
dev_dependencies:
flutter_test:
sdk: flutter
coverage: ^1.6.3
运行命令
bash
# 运行测试并收集覆盖率
flutter test --coverage
# 生成 HTML 报告
genhtml coverage/lcov.info -o coverage/html
覆盖率类型
1. 行覆盖率
dart
// 统计每行代码是否被执行
@override
Widget build(BuildContext context) {
if (condition) { // 已覆盖
return Widget1(); // 已覆盖
} else {
return Widget2(); // 未覆盖
}
}
2. 分支覆盖率
dart
String getMessage(int value) {
if (value > 0) { // true 分支已覆盖
return 'Positive';
} else if (value < 0) { // false 分支未覆盖
return 'Negative';
} else { // 未覆盖
return 'Zero';
}
}
完整示例
dart
// 被测试的类
class Calculator {
int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;
int multiply(int a, int b) => a * b;
double divide(int a, int b) {
if (b == 0) {
throw ArgumentError('Cannot divide by zero');
}
return a / b;
}
bool isEven(int number) => number % 2 == 0;
String getNumberType(int number) {
if (number > 0) {
return 'Positive';
} else if (number < 0) {
return 'Negative';
} else {
return 'Zero';
}
}
}
// 测试文件
void main() {
group('Calculator', () {
late Calculator calculator;
setUp(() {
calculator = Calculator();
});
group('add', () {
test('should add two positive numbers', () {
expect(calculator.add(2, 3), equals(5));
});
test('should add positive and negative numbers', () {
expect(calculator.add(2, -3), equals(-1));
});
});
group('divide', () {
test('should divide two numbers', () {
expect(calculator.divide(6, 2), equals(3));
});
test('should throw error when dividing by zero', () {
expect(
() => calculator.divide(6, 0),
throwsA(isA<ArgumentError>()),
);
});
});
group('getNumberType', () {
test('should return Positive for positive numbers', () {
expect(calculator.getNumberType(5), equals('Positive'));
});
test('should return Negative for negative numbers', () {
expect(calculator.getNumberType(-5), equals('Negative'));
});
test('should return Zero for zero', () {
expect(calculator.getNumberType(0), equals('Zero'));
});
});
});
}
覆盖率报告
1. 命令行报告
bash
# 显示覆盖率摘要
flutter test --coverage --coverage-path=coverage/lcov.info
# 生成详细报告
genhtml coverage/lcov.info -o coverage/html
2. HTML 报告
html
<!DOCTYPE html>
<html>
<head>
<title>Coverage Report</title>
</head>
<body>
<div class="coverage-report">
<h1>Coverage Report</h1>
<div class="summary">
<p>Line Coverage: 85%</p>
<p>Branch Coverage: 75%</p>
<p>Function Coverage: 90%</p>
</div>
<!-- 详细报告内容 -->
</div>
</body>
</html>
CI/CD 集成
GitHub Actions
yaml
name: Test Coverage
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.x'
- name: Install dependencies
run: flutter pub get
- name: Run tests with coverage
run: flutter test --coverage
- name: Upload coverage reports
uses: codecov/codecov-action@v3
with:
file: coverage/lcov.info
最佳实践
- 设置覆盖率目标
dart
// 在 CI 中设置最低覆盖率要求
final minimumCoverage = 80.0;
final coverage = calculateCoverage();
if (coverage < minimumCoverage) {
throw Exception('Coverage below minimum threshold');
}
- 忽略不需要测试的代码
dart
// coverage:ignore-start
void debugLog(String message) {
print(message);
}
// coverage:ignore-end
- 定期检查覆盖率趋势
dart
class CoverageTrend {
final DateTime date;
final double coverage;
CoverageTrend(this.date, this.coverage);
static void trackCoverage(double coverage) {
// 记录覆盖率历史
}
}
注意事项
- 合理设置覆盖率目标
- 关注关键代码覆盖
- 避免为覆盖率而测试
- 定期更新测试用例
- 维护测试质量
总结
测试覆盖率是衡量测试质量的重要指标,但不是唯一指标。通过合理的覆盖率统计和分析,可以帮助我们发现测试盲点,提高代码质量。