前言
一般来说,经过充分测试的应用程序具有许多单元测试和widget测试,通过代码覆盖率进行跟踪,另外还具有足够的集成测试来涵盖所有重要的用例。
单元测试
单元测试测试一个类,一个方法逻辑。test这个package提供了写单测的核心框架,flutter_test这个package则提供了额外的功能来测试widget。步骤如下:
- 将 test 或者 flutter_test 加入依赖。
- 创建测试文件。
- 创建一个要测试的类。
- 为创建的类写一个测试。
- 整合多个测试到一个group。如果你想运行多个有关联或者一个系列的测试,可以使用test package提供的group函数将他们整合到一起。你可以用flutter test运行同一个组的所有测试。
- 执行测试。 示例代码如下:
class Counter {
int value = 0;
void increment() => value++;
void decrement() => value--;
}
// Import the test package and Counter class
import 'package:test/test.dart';
import 'package:testing_app/counter.dart';
void main() {
group('Test start, increment, decrement', () {
test('value should start at 0', () {
expect(Counter().value, 0);
});
test('value should be incremented', () {
final counter = Counter();
counter.increment();
expect(counter.value, 1);
});
test('value should be decremented', () {
final counter = Counter();
counter.decrement();
expect(counter.value, -1);
});
});
}
单元测试可以通过命令行执行,也可以通过IDE直接运行。命令行执行如下图所示:
代码执行结果如下图所示:
widget测试
测试一个单独的widget。为了测试widget类,我们需要使用flutter_test这个package提供的额外工具,这些工具是跟Flutter SDK一起发布的。widget测试步骤如下:
- 添加一个flutter_test依赖。
- 创建一个测试用的widget。
- 创建一个testWidgets测试方法。
- 使用WidgetTester建立widget。
- 使用Finder查找widget。
- 使用Matcher验证widget是否正常工作。 完整示例代码如下:
import 'package:flutter/material.dart';
class MyWidget extends StatelessWidget {
const MyWidget({super.key, required this.title, required this.message});
final String title;
final String message;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(child: Text(message)),
),
);
}
}
import 'package:flutter_test/flutter_test.dart';
import 'package:testing_app/my_widget.dart';
void main() {
testWidgets('MyWidget has a title and message', (tester) async {
await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));
final titleFinder = find.text('T');
final messageFinder = find.text('M');
// Use the `findsOneWidget` matcher provided by flutter_test to verify
// that the Text widgets appear exactly once in the widget tree.
expect(titleFinder, findsOneWidget);
expect(messageFinder, findsOneWidget);
});
}
注意Matcher的种类: findsNothing 验证没有可被查找的widgets。 findsWidgets 验证一个或多个widgets被找到。 findsNWidgets 验证特定数量的widgets被找到。
集成测试
测试一个完整的app或者一个app的大部分功能。 示例代码如下:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Counter App',
home: MyHomePage(title: 'Counter App Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
// Provide a Key to this button. This allows finding this
// specific button inside the test suite, and tapping it.
key: const Key('increment'),
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:testing_app/main.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end-to-end test', () {
testWidgets('tap on the floating action button, verify counter', (
tester,
) async {
// Load app widget.
await tester.pumpWidget(const MyApp());
// Verify the counter starts at 0.
expect(find.text('0'), findsOneWidget);
// Finds the floating action button to tap on.
final fab = find.byKey(const ValueKey('increment'));
// Emulate a tap on the floating action button.
await tester.tap(fab);
// Trigger a frame.
await tester.pumpAndSettle();
// Verify the counter increments by 1.
expect(find.text('1'), findsOneWidget);
});
});
}
基准测试
性能对于移动应用用户来说相当重要,用户希望应用程序有流畅的滚动和优雅的动画,不愿看到卡顿和掉帧现象。我们如何确保我们的应用程序在各种设备上不会受到卡顿的影响?
以下两种方式可供选择:首先,我们可以在不同的设备对应用程序进行手动测试。这种方式适用于较小的应用程序,但随着应用程序扩展性的提升,它将变得更加繁琐。另外,我们可以运行集成测试,执行特定任务并记录性能时间轴。然后,我们可以检验结果,以确定是否需要对我们应用程序的特定部分进行改善。
单个方法时间耗时:
void main() {
final sw = Stopwatch()..start();
for (int i = 0; i < 100000; i++) {
final _ = i * 2;
}
sw.stop();
print('Elapsed: ${sw.elapsedMicroseconds}µs');
}