Flutter测试

102 阅读2分钟

前言

一般来说,经过充分测试的应用程序具有许多单元测试和widget测试,通过代码覆盖率进行跟踪,另外还具有足够的集成测试来涵盖所有重要的用例。

单元测试

单元测试测试一个类,一个方法逻辑。test这个package提供了写单测的核心框架,flutter_test这个package则提供了额外的功能来测试widget。步骤如下:

  1. 将 test 或者 flutter_test 加入依赖。
  2. 创建测试文件。
  3. 创建一个要测试的类。
  4. 为创建的类写一个测试。
  5. 整合多个测试到一个group。如果你想运行多个有关联或者一个系列的测试,可以使用test package提供的group函数将他们整合到一起。你可以用flutter test运行同一个组的所有测试。
  6. 执行测试。 示例代码如下:
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直接运行。命令行执行如下图所示:

image.png 代码执行结果如下图所示:

image.png

widget测试

测试一个单独的widget。为了测试widget类,我们需要使用flutter_test这个package提供的额外工具,这些工具是跟Flutter SDK一起发布的。widget测试步骤如下:

  1. 添加一个flutter_test依赖。
  2. 创建一个测试用的widget。
  3. 创建一个testWidgets测试方法。
  4. 使用WidgetTester建立widget。
  5. 使用Finder查找widget。
  6. 使用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');
}

参考资料

docs.flutter.cn/testing/ove…