测试
测试是保证代码质量的重要手段。Flutter 提供了三种测试级别:单元测试、Widget 测试和集成测试。
测试分类
| 类型 | 测试对象 | 运行速度 | 依赖 |
|---|---|---|---|
| 单元测试 | 函数、方法、类 | 毫秒级 | 无 UI |
| Widget 测试 | 单个 Widget | 秒级 | Flutter 框架 |
| 集成测试 | 完整 App 流程 | 分钟级 | 真实环境 |
单元测试
测试纯逻辑代码,不涉及 UI。
基本写法
dart
// test/utils/validators_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/utils/validators.dart';
void main() {
group('Validators', () {
test('required 返回 null 当值非空', () {
expect(Validators.required('hello'), isNull);
});
test('required 返回错误提示 当值为空', () {
expect(Validators.required(''), '此项不能为空');
expect(Validators.required(null), '此项不能为空');
});
test('email 返回 null 当格式正确', () {
expect(Validators.email('test@example.com'), isNull);
});
test('email 返回错误提示 当格式错误', () {
expect(Validators.email('invalid'), '邮箱格式不正确');
});
});
}测试 Model
dart
// test/models/user_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/models/user.dart';
void main() {
group('User', () {
test('fromJson 正确解析', () {
final json = {'name': '张三', 'age': 25};
final user = User.fromJson(json);
expect(user.name, '张三');
expect(user.age, 25);
});
test('toJson 正确序列化', () {
final user = User(name: '张三', age: 25);
final json = user.toJson();
expect(json['name'], '张三');
expect(json['age'], 25);
});
});
}运行测试
bash
# 运行所有测试
flutter test
# 运行指定文件
flutter test test/utils/validators_test.dart
# 运行匹配名称的测试
flutter test --name "email"Widget 测试
测试单个 Widget 的渲染和交互。
基本写法
dart
// test/widgets/counter_display_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/widgets/counter_display.dart';
void main() {
testWidgets('CounterDisplay 显示正确的数字', (WidgetTester tester) async {
// 1. 构建 Widget
await tester.pumpWidget(
MaterialApp(home: CounterDisplay(count: 42)),
);
// 2. 查找 Widget
final textFinder = find.text('42');
// 3. 验证
expect(textFinder, findsOneWidget);
});
testWidgets('点击按钮触发回调', (WidgetTester tester) async {
var pressed = false;
await tester.pumpWidget(
MaterialApp(
home: ElevatedButton(
onPressed: () => pressed = true,
child: Text('点击'),
),
),
);
// 模拟点击
await tester.tap(find.text('点击'));
expect(pressed, isTrue);
});
}常用 Finder
| Finder | 说明 |
|---|---|
find.text('hello') | 查找包含指定文字的 Widget |
find.byType(Text) | 查找指定类型的 Widget |
find.byKey(Key('myKey')) | 通过 Key 查找 |
find.byIcon(Icons.add) | 查找指定图标 |
find.widgetWithText(Card, '标题') | 查找包含文字的特定类型 Widget |
常用 Matcher
| Matcher | 说明 |
|---|---|
findsOneWidget | 找到恰好一个 |
findsNothing | 没找到 |
findsN(3) | 找到 N 个 |
findsWidgets | 找到至少一个 |
Mock 测试
使用 Mock 替换真实依赖(如网络请求),让测试更可控。
安装
bash
flutter pub add dev:mockito使用
dart
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
// 生成 Mock 类
@GenerateNiceMocks([MockSpec<ApiService>()])
import 'api_service_test.mocks.dart';
void main() {
test('fetchUsers 返回用户列表', () async {
// 创建 Mock
final mockApi = MockApiService();
when(mockApi.getUsers()).thenAnswer((_) async => [User(name: '张三', age: 25)]);
// 使用 Mock
final users = await mockApi.getUsers();
expect(users.length, 1);
expect(users[0].name, '张三');
});
}测试最佳实践
- 测试文件放在
test/目录下,与lib/目录结构对应 - 文件名以
_test.dart结尾,如validators_test.dart - 使用
group组织相关测试 - 测试命名清晰,描述期望行为
- 每个测试只测一件事
- 异步测试使用
async/await - 使用 Mock 隔离外部依赖
建议
- 先学会写单元测试(最简单、最实用)
- Widget 测试在关键组件上写即可
- 集成测试在发布前跑一遍核心流程
- 不要追求 100% 覆盖率,优先测试核心业务逻辑
