1. はじめに
Flutter は、1 つのコードベースからネイティブにコンパイルして、モバイル、ウェブ、デスクトップの美しいアプリケーションを作成できる Google の UI ツールキットです。
この Codelab では、シンプルな Flutter アプリを作成してテストします。アプリでは、Provider パッケージを使用して状態を管理します。
学習内容
- ウィジェット テスト フレームワークを使用してウィジェット テストを作成する方法
integration_test
ライブラリを使用してアプリの UI とパフォーマンスをテストする統合テストの作成方法- 単体テストを使用してデータクラス(プロバイダ)をテストする方法
作成するアプリの概要
この Codelab では、最初に項目のリストがあるシンプルなアプリケーションを作成します。ソースコードは用意されているので、すぐにテストを開始できます。このアプリでは次の操作がサポートされます。
- お気に入りへの項目の追加
- お気に入りリストの表示
- お気に入りリストからの項目の削除
アプリが完成したら、次のテストを作成します。
| Android で動作中のアプリの GIF |
この Codelab で学びたいことは次のどれですか?
2. Flutter の開発環境をセットアップする
このラボを完了するには、Flutter SDK とエディタの 2 つのソフトウェアが必要です。
この Codelab は、次のいずれかのデバイスを使って実行できます。
- パソコンに接続され、デベロッパー モードに設定された物理デバイス(Android または iOS)
- iOS シミュレータ(Xcode ツールのインストールが必要)
- Android Emulator(Android Studio でセットアップが必要)
- ブラウザ(デバッグには Chrome が必要)
- Windows、Linux、macOS のデスクトップ アプリケーション。開発はデプロイする予定のプラットフォームで行う必要があります。たとえば、Windows のデスクトップ アプリを開発する場合は、適切なビルドチェーンにアクセスできるように Windows で開発する必要があります。オペレーティング システム固有の要件については、docs.flutter.dev/desktop に詳しい説明があります。
3. 始めるにあたって
新しい Flutter アプリを作成し、依存関係を更新する
この Codelab では、Flutter モバイルアプリのテストを中心に説明します。テストするアプリは、ソースファイルを切り貼りするだけで、すぐに作成できます。そのほかにも、各種のテストについて説明します。
初めての Flutter アプリについての手順に沿って、または次のコマンドラインで、テンプレート化された簡単な Flutter アプリを作成します。
$ flutter create testing_app
コマンドラインで pub 依存関係を追加します。状態管理を容易にするために、次のように provider
を追加します。
$ cd testing_app $ flutter pub add provider Resolving dependencies... collection 1.17.0 (1.17.1 available) js 0.6.5 (0.6.7 available) matcher 0.12.13 (0.12.14 available) meta 1.8.0 (1.9.0 available) + nested 1.0.0 path 1.8.2 (1.8.3 available) + provider 6.0.5 test_api 0.4.16 (0.4.18 available) Changed 2 dependencies!
デバイスまたはエミュレータで Flutter コードの自動テストを行うために、次のように integration_test
を追加します。
$ flutter pub add --dev --sdk=flutter integration_test Resolving dependencies... + archive 3.3.2 (3.3.6 available) collection 1.17.0 (1.17.1 available) + crypto 3.0.2 + file 6.1.4 + flutter_driver 0.0.0 from sdk flutter + fuchsia_remote_debug_protocol 0.0.0 from sdk flutter + integration_test 0.0.0 from sdk flutter js 0.6.5 (0.6.7 available) matcher 0.12.13 (0.12.14 available) meta 1.8.0 (1.9.0 available) path 1.8.2 (1.8.3 available) + platform 3.1.0 + process 4.2.4 + sync_http 0.3.1 test_api 0.4.16 (0.4.18 available) + typed_data 1.3.1 + vm_service 9.4.0 (11.0.1 available) + webdriver 3.0.1 (3.0.2 available) Changed 12 dependencies!
実際のデバイスとエミュレータで動作する Flutter アプリケーションをテストするための高度な API のために、次のように flutter_driver
を追加します。
$ flutter pub add --dev --sdk=flutter flutter_driver Resolving dependencies... archive 3.3.2 (3.3.6 available) collection 1.17.0 (1.17.1 available) js 0.6.5 (0.6.7 available) matcher 0.12.13 (0.12.14 available) meta 1.8.0 (1.9.0 available) path 1.8.2 (1.8.3 available) test_api 0.4.16 (0.4.18 available) vm_service 9.4.0 (11.0.1 available) webdriver 3.0.1 (3.0.2 available) Got dependencies!
一般的なテストツールのために、次のように test
を追加します。
$ flutter pub add --dev test Resolving dependencies... + _fe_analyzer_shared 52.0.0 + analyzer 5.4.0 archive 3.3.2 (3.3.6 available) + args 2.3.2 collection 1.17.0 (1.17.1 available) + convert 3.1.1 + coverage 1.6.3 + frontend_server_client 3.2.0 + glob 2.1.1 + http_multi_server 3.2.1 + http_parser 4.0.2 + io 1.0.4 js 0.6.5 (0.6.7 available) + logging 1.1.1 matcher 0.12.13 (0.12.14 available) meta 1.8.0 (1.9.0 available) + mime 1.0.4 + node_preamble 2.0.1 + package_config 2.1.0 path 1.8.2 (1.8.3 available) + pool 1.5.1 + pub_semver 2.1.3 + shelf 1.4.0 + shelf_packages_handler 3.0.1 + shelf_static 1.1.1 + shelf_web_socket 1.0.3 + source_map_stack_trace 2.1.1 + source_maps 0.10.11 + test 1.22.0 (1.23.0 available) test_api 0.4.16 (0.4.18 available) + test_core 0.4.20 (0.4.23 available) vm_service 9.4.0 (11.0.1 available) + watcher 1.0.2 + web_socket_channel 2.3.0 webdriver 3.0.1 (3.0.2 available) + webkit_inspection_protocol 1.2.0 + yaml 3.1.1 Changed 28 dependencies!
アプリ ナビゲーションの処理のために、次のように go_router を追加します。
$ flutter pub add go_router Resolving dependencies... archive 3.3.2 (3.3.6 available) collection 1.17.0 (1.17.1 available) + flutter_web_plugins 0.0.0 from sdk flutter + go_router 6.0.4 js 0.6.5 (0.6.7 available) matcher 0.12.13 (0.12.14 available) meta 1.8.0 (1.9.0 available) path 1.8.2 (1.8.3 available) test 1.22.0 (1.23.0 available) test_api 0.4.16 (0.4.18 available) test_core 0.4.20 (0.4.23 available) vm_service 9.4.0 (11.0.1 available) webdriver 3.0.1 (3.0.2 available) Changed 2 dependencies!
以下の依存関係が pubspec.yaml に追加されます。
dependencies
配下:
dependencies: provider: ^6.0.5 go_router: ^6.0.4
dev_dependencies
配下:
dev_dependencies: integration_test: sdk: flutter flutter_driver: sdk: flutter test: ^1.22.0
任意のコードエディタでプロジェクトを開いてアプリを実行するか、次のようにコマンドラインで実行します。
$ flutter run
4. アプリを作成する
次に、アプリを作成して、テストできるようにします。アプリには次のファイルが含まれています。
lib/models/favorites.dart
- お気に入りリストのモデルクラスを作成します。lib/screens/favorites.dart
- お気に入りリストのレイアウトを作成します。lib/screens/home.dart
- 項目のリストを作成します。lib/main.dart
- アプリを起動するメインファイルです。
まず lib/models/favorites.dart
に Favorites
モデルを作成する
lib
ディレクトリに models
という新しいディレクトリを作成してから、favorites.dart
という新しいファイルを作成します。このファイルに次のコードを追加します。
lib/models/favorites.dart
import 'package:flutter/material.dart';
/// The [Favorites] class holds a list of favorite items saved by the user.
class Favorites extends ChangeNotifier {
final List<int> _favoriteItems = [];
List<int> get items => _favoriteItems;
void add(int itemNo) {
_favoriteItems.add(itemNo);
notifyListeners();
}
void remove(int itemNo) {
_favoriteItems.remove(itemNo);
notifyListeners();
}
}
lib/screens/favorites.dart
にお気に入りページを追加する
lib
ディレクトリに screens
という新しいディレクトリを作成し、そのディレクトリに favorites.dart
という新しいファイルを作成します。このファイルに次のコードを追加します。
lib/screens/favorites.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/favorites.dart';
class FavoritesPage extends StatelessWidget {
const FavoritesPage({super.key});
static String routeName = 'favorites_page';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Favorites'),
),
body: Consumer<Favorites>(
builder: (context, value, child) => ListView.builder(
itemCount: value.items.length,
padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (context, index) => FavoriteItemTile(value.items[index]),
),
),
);
}
}
class FavoriteItemTile extends StatelessWidget {
const FavoriteItemTile(this.itemNo, {super.key});
final int itemNo;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.primaries[itemNo % Colors.primaries.length],
),
title: Text(
'Item $itemNo',
key: Key('favorites_text_$itemNo'),
),
trailing: IconButton(
key: Key('remove_icon_$itemNo'),
icon: const Icon(Icons.close),
onPressed: () {
Provider.of<Favorites>(context, listen: false).remove(itemNo);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Removed from favorites.'),
duration: Duration(seconds: 1),
),
);
},
),
),
);
}
}
lib/screens/home.dart
にホームページを追加する
lib/screens
ディレクトリに、home.dart
という名前の新しいファイルを作成します。lib/screens/home.dart
に、次のコードを追加します。
lib/screens/home.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../models/favorites.dart';
import 'favorites.dart';
class HomePage extends StatelessWidget {
static String routeName = '/';
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Testing Sample'),
actions: <Widget>[
TextButton.icon(
onPressed: () {
context.go('/${FavoritesPage.routeName}');
},
icon: const Icon(Icons.favorite_border),
label: const Text('Favorites'),
),
],
),
body: ListView.builder(
itemCount: 100,
cacheExtent: 20.0,
padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (context, index) => ItemTile(index),
),
);
}
}
class ItemTile extends StatelessWidget {
final int itemNo;
const ItemTile(this.itemNo, {super.key});
@override
Widget build(BuildContext context) {
var favoritesList = Provider.of<Favorites>(context);
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.primaries[itemNo % Colors.primaries.length],
),
title: Text(
'Item $itemNo',
key: Key('text_$itemNo'),
),
trailing: IconButton(
key: Key('icon_$itemNo'),
icon: favoritesList.items.contains(itemNo)
? const Icon(Icons.favorite)
: const Icon(Icons.favorite_border),
onPressed: () {
!favoritesList.items.contains(itemNo)
? favoritesList.add(itemNo)
: favoritesList.remove(itemNo);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(favoritesList.items.contains(itemNo)
? 'Added to favorites.'
: 'Removed from favorites.'),
duration: const Duration(seconds: 1),
),
);
},
),
),
);
}
}
lib/main.dart
の内容を置き換える
lib/main.dart
の内容を次のコードに置き換えます。
lib/main.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'models/favorites.dart';
import 'screens/favorites.dart';
import 'screens/home.dart';
void main() {
runApp(const TestingApp());
}
final _router = GoRouter(
routes: [
GoRoute(
path: HomePage.routeName,
builder: (context, state) {
return const HomePage();
},
routes: [
GoRoute(
path: FavoritesPage.routeName,
builder: (context, state) {
return const FavoritesPage();
},
),
],
),
],
);
class TestingApp extends StatelessWidget {
const TestingApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Favorites>(
create: (context) => Favorites(),
child: MaterialApp.router(
title: 'Testing Sample',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
routerConfig: _router,
),
);
}
}
これでアプリは完成しましたが、テストされていません。
アプリを実行します。次のスクリーンショットのようになります。
アプリに項目のリストが表示されます。いずれかの行にあるハート型のアイコンをタップして、ハートを塗りつぶされた状態にし、その項目をお気に入りリストに追加します。AppBar
の [Favorites] ボタンをクリックすると、お気に入りのリストを含む、2 番目の画面が表示されます。
これで、アプリのテスト準備が整いました。次のステップでアプリのテストを始めます。
5. プロバイダの単体テスト
favorites
モデルの単体テストから始めます。単体テストとは、どんなものでしょうか。単体テストでは、ソフトウェアの各単位(関数やオブジェクト、ウィジェット)で目的のタスクが正しく実行されることを確認します。
Flutter アプリ内のすべてのテストファイル(統合テストを除く)は test
ディレクトリに配置されます。
test/widget_test.dart
を削除する
テストを開始する前に、widget_test.dart
ファイルを削除します。これから独自のテストファイルを追加します。
新しいテストファイルを作成する
まず、Favorites
モデル内の add()
メソッドのテストとして、新しい項目がリストに追加され、リストの変更が反映されていることを確認します。慣例として、test
ディレクトリのディレクトリ構造は lib
ディレクトリと同じにして、Dart ファイルの名前は同じものに _test
を付加します。
test
ディレクトリに models
ディレクトリを作成します。この新しいディレクトリに、次の内容の favorites_test.dart
ファイルを作成します。
test/models/favorites_test.dart
import 'package:test/test.dart';
import 'package:testing_app/models/favorites.dart';
void main() {
group('Testing App Provider', () {
var favorites = Favorites();
test('A new item should be added', () {
var number = 35;
favorites.add(number);
expect(favorites.items.contains(number), true);
});
});
}
Flutter テスト フレームワークでは、グループ内で互いに関連した類似のテストをまとめることができます。1 つのテストファイルに複数のグループを含めると、lib
ディレクトリ内の対応するファイルの別々の部分をテストできます。
test()
メソッドは、位置に意味のある 2 つのパラメータを取ります。テストの description
と、実際のテストを記述する callback
です。
リストからの項目の削除をテストします。同じ Testing App Provider
グループに次のテストを挿入します。
test/models/favorites_test.dart
test('An item should be removed', () {
var number = 45;
favorites.add(number);
expect(favorites.items.contains(number), true);
favorites.remove(number);
expect(favorites.items.contains(number), false);
});
テストを実施する
コマンドラインでプロジェクトのルート ディレクトリに移動し、次のコマンドを入力します。
$ flutter test test/models/favorites_test.dart
すべてが問題なく動作した場合は、次のようなメッセージが表示されます。
00:06 +2: All tests passed!
完全なテストファイルは、こちらのリンク(test/models/favorites_test.dart
)から入手できます。
単体テストの詳細については、「単体テストの概要」をご覧ください。
6. ウィジェットのテスト
このステップでは、ウィジェットをテストするコードを追加します。ウィジェット テストは Flutter に固有のテストであり、各ウィジェットを個別にテストできます。このステップでは、HomePage
と FavoritesPage
の画面を別々にテストします。
ウィジェット テストでは、test()
関数の代わりに testWidget()
関数を使用します。test()
関数と同様に、testWidget()
関数は description,
と callback
の 2 つのパラメータを取りますが、コールバックは引数として WidgetTester
を取ります。
ウィジェット テストでは TestFlutterWidgetsBinding
を使用します。このクラスは、実行中のアプリと同じリソース(画面サイズ、アニメーションのスケジュールに関する情報など)を提供しますが、実際のアプリは使用しません。代わりに、仮想環境を使用してウィジェットをインスタンス化し、結果をテストします。pumpWidget
は、アプリケーションで行うのと同じように、特定のウィジェットを用意して測定するようにフレームワークに指示することで、この処理を開始します。
ウィジェット テスト フレームワークには、ウィジェットを探すためのファインダー(text()
、byType()
、byIcon().
など)や、結果を確認するためのマッチャーが用意されています。
まず、HomePage
ウィジェットをテストします。
新しいテストファイルを作成する
最初のテストでは、HomePage
のスクロールが正しく動作するかどうかを確認します。
test
ディレクトリに新しいファイルを作成し、home_test.dart
という名前を付けます。新しく作成したファイルに、次のコードを追加します。
test/home_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:testing_app/models/favorites.dart';
import 'package:testing_app/screens/home.dart';
Widget createHomeScreen() => ChangeNotifierProvider<Favorites>(
create: (context) => Favorites(),
child: const MaterialApp(
home: HomePage(),
),
);
void main() {
group('Home Page Widget Tests', () {
testWidgets('Testing Scrolling', (tester) async {
await tester.pumpWidget(createHomeScreen());
expect(find.text('Item 0'), findsOneWidget);
await tester.fling(
find.byType(ListView),
const Offset(0, -200),
3000,
);
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsNothing);
});
});
}
createHomeScreen()
関数では、テストするウィジェットが MaterialApp に作成され、ChangeNotifierProvider でラップされます。HomePage ウィジェットが両方のウィジェットを継承し、それらが提供するデータにアクセスできるように、両方のウィジェットはウィジェット ツリーで上にある必要があります。この関数は、パラメータとして pumpWidget()
関数に渡されます。
次に、画面にレンダリングされた ListView
をフレームワークが検出できるかどうかをテストします。
次のコード スニペットを home_test.dart
に追加します。
test/home_test.dart
group('Home Page Widget Tests', () {
// BEGINNING OF NEW CONTENT
testWidgets('Testing if ListView shows up', (tester) async {
await tester.pumpWidget(createHomeScreen());
expect(find.byType(ListView), findsOneWidget);
});
// END OF NEW CONTENT
testWidgets('Testing Scrolling', (tester) async {
await tester.pumpWidget(createHomeScreen());
expect(find.text('Item 0'), findsOneWidget);
await tester.fling(
find.byType(ListView),
const Offset(0, -200),
3000,
);
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsNothing);
});
});
テストを実施する
まず、次のコマンドを使い、単体テストの実行と同じやり方でテストを実行します。
$ flutter test test/home_test.dart
テストはすぐに実行され、次のようなメッセージが表示されます。
00:02 +2: All tests passed!
デバイスまたはエミュレータを使用してウィジェット テストを実行することもでき、それによってテストの実行状況を確認できるようになります。ホット リスタートも可能になります。
デバイスを接続するか、エミュレータを起動します。デスクトップ アプリケーションとしてテストを実行することもできます。
コマンドラインからプロジェクトのルート ディレクトリに移動し、次のコマンドを入力します。
$ flutter run test/home_test.dart
テストを実行するデバイスの選択が必要な場合があります。その場合は、次の手順に沿ってデバイスを選択します。
Multiple devices found:
Linux (desktop) • linux • linux-x64 • Ubuntu 22.04.1 LTS 5.15.0-58-generic
Chrome (web) • chrome • web-javascript • Google Chrome 109.0.5414.119
[1]: Linux (linux)
[2]: Chrome (chrome)
Please choose one (To quit, press "q/Q"):
すべてが問題なく動作した場合は、次のような出力が表示されます。
Launching test/home_test.dart on Linux in debug mode...
Building Linux application...
flutter: 00:00 +0: Home Page Widget Tests Testing if ListView shows up
Syncing files to device Linux... 62ms
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
💪 Running with sound null safety 💪
An Observatory debugger and profiler on Linux is available at: http://127.0.0.1:35583/GCpdLBqf2UI=/
flutter: 00:00 +1: Home Page Widget Tests Testing Scrolling
The Flutter DevTools debugger and profiler on Linux is available at:
http://127.0.0.1:9100?uri=http://127.0.0.1:35583/GCpdLBqf2UI=/
flutter: 00:02 +2: All tests passed!
次に、テストファイルを変更して Shift + R
を押し、アプリをホット リスタートしてすべてのテストを再実行します。アプリケーションを停止しないでください。
HomePage ウィジェットをテストするグループにテストを追加します。次のテストをファイルにコピーします。
test/home_test.dart
testWidgets('Testing IconButtons', (tester) async {
await tester.pumpWidget(createHomeScreen());
expect(find.byIcon(Icons.favorite), findsNothing);
await tester.tap(find.byIcon(Icons.favorite_border).first);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Added to favorites.'), findsOneWidget);
expect(find.byIcon(Icons.favorite), findsWidgets);
await tester.tap(find.byIcon(Icons.favorite).first);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Removed from favorites.'), findsOneWidget);
expect(find.byIcon(Icons.favorite), findsNothing);
});
このテストでは、IconButton
をタップすると Icons.favorite_border
(塗りつぶされていないハート)から Icons.favorite
(塗りつぶされているハート)に変わり、もう一度タップすると Icons.favorite_border
に戻ることを確認します。
Shift + R
を入力します。これにより、アプリがホット リスタートされ、すべてのテストが再実行されます。
完全なテストファイルは、こちらのリンク(test/home_test.dart
.
)から入手できます。
次のコードを使用し、同じ処理で FavoritesPage
をテストします。これを同じ手順で実行します。
test/favorites_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:testing_app/models/favorites.dart';
import 'package:testing_app/screens/favorites.dart';
late Favorites favoritesList;
Widget createFavoritesScreen() => ChangeNotifierProvider<Favorites>(
create: (context) {
favoritesList = Favorites();
return favoritesList;
},
child: const MaterialApp(
home: FavoritesPage(),
),
);
void addItems() {
for (var i = 0; i < 10; i += 2) {
favoritesList.add(i);
}
}
void main() {
group('Favorites Page Widget Tests', () {
testWidgets('Test if ListView shows up', (tester) async {
await tester.pumpWidget(createFavoritesScreen());
addItems();
await tester.pumpAndSettle();
expect(find.byType(ListView), findsOneWidget);
});
testWidgets('Testing Remove Button', (tester) async {
await tester.pumpWidget(createFavoritesScreen());
addItems();
await tester.pumpAndSettle();
var totalItems = tester.widgetList(find.byIcon(Icons.close)).length;
await tester.tap(find.byIcon(Icons.close).first);
await tester.pumpAndSettle();
expect(tester.widgetList(find.byIcon(Icons.close)).length,
lessThan(totalItems));
expect(find.text('Removed from favorites.'), findsOneWidget);
});
});
}
このテストでは、閉じる(削除)ボタンが押されたときに項目が消えるかどうかを確認します。
ウィジェット テストの詳細については、以下をご覧ください。
7. アプリの UI を統合テストでテストする
統合テストは、アプリの各部分がどのように連携するかをテストするために使用されます。integration_test
ライブラリは Flutter での統合テストに使用されます。これは Flutter 版の Selenium WebDriver、Protractor、Espresso、Earl Gray です。このパッケージは、デバイス上のテストを制御するために、内部で flutter_driver
を使用しています。
Flutter での統合テストを記述することは、ウィジェット テストに似ていますが、統合テストは、対象デバイスと呼ばれるモバイル デバイス、ブラウザ、またはデスクトップ アプリケーションで実行されます。
テストを作成する
プロジェクトのルート ディレクトリに integration_test
というディレクトリを作成し、そのディレクトリで app_test.dart
という新しいファイルを作成します。
integration_test/app_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:testing_app/main.dart';
void main() {
group('Testing App', () {
testWidgets('Favorites operations test', (tester) async {
await tester.pumpWidget(const TestingApp());
final iconKeys = [
'icon_0',
'icon_1',
'icon_2',
];
for (var icon in iconKeys) {
await tester.tap(find.byKey(ValueKey(icon)));
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Added to favorites.'), findsOneWidget);
}
await tester.tap(find.text('Favorites'));
await tester.pumpAndSettle();
final removeIconKeys = [
'remove_icon_0',
'remove_icon_1',
'remove_icon_2',
];
for (final iconKey in removeIconKeys) {
await tester.tap(find.byKey(ValueKey(iconKey)));
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Removed from favorites.'), findsOneWidget);
}
});
});
}
テストを実施する
デバイスを接続するか、エミュレータを起動します。デスクトップ アプリケーションとしてテストを実行することもできます。
コマンドラインでプロジェクトのルート ディレクトリに移動し、次のコマンドを入力します。
$ flutter test integration_test/app_test.dart
すべてが問題なく動作した場合は、次のような出力が表示されます。
Multiple devices found:
Linux (desktop) • linux • linux-x64 • Ubuntu 22.04.1 LTS 5.15.0-58-generic
Chrome (web) • chrome • web-javascript • Google Chrome 109.0.5414.119
[1]: Linux (linux)
[2]: Chrome (chrome)
Please choose one (To quit, press "q/Q"): 1
00:00 +0: loading /home/miquel/tmp/testing_app/integration_test/app_test.dart B00:08 +0: loading /home/miquel/tmp/testing_app/integration_test/app_test.dart
00:26 +1: All tests passed!
8. Flutter Driver でアプリのパフォーマンスをテストする
パフォーマンス テストを記述する
integration_test フォルダに、次の内容の perf_test.dart という新しいテストファイルを作成します。
integration_test/perf_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:testing_app/main.dart';
void main() {
group('Testing App Performance', () {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
testWidgets('Scrolling test', (tester) async {
await tester.pumpWidget(const TestingApp());
final listFinder = find.byType(ListView);
await binding.traceAction(() async {
await tester.fling(listFinder, const Offset(0, -500), 10000);
await tester.pumpAndSettle();
await tester.fling(listFinder, const Offset(0, 500), 10000);
await tester.pumpAndSettle();
}, reportKey: 'scrolling_summary');
});
});
}
ensureInitialized()
関数では、統合テストドライバが初期化済みかどうかを確認し、必要に応じて初期化をやり直します。アニメーションのあるコードをテストする場合は、framePolicy
を fullyLive
に設定することをおすすめします。
このテストでは、項目のリストを高速に最後までスクロールしてから、スクロールで元に戻します。traceAction()
関数がアクションを記録し、タイムライン サマリーを生成します。
パフォーマンスに関する結果をキャプチャする
結果をキャプチャするには、test_driver
というフォルダを作成し、そこに perf_driver.dart
というファイルを作成して、次のコードを追加します。
test_driver/perf_driver.dart
import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() {
return integrationDriver(
responseDataCallback: (data) async {
if (data != null) {
final timeline = driver.Timeline.fromJson(
data['scrolling_summary'] as Map<String, dynamic>);
final summary = driver.TimelineSummary.summarize(timeline);
await summary.writeTimelineToFile(
'scrolling_summary',
pretty: true,
includeSummary: true,
);
}
},
);
}
テストを実施する
デバイスを接続するか、エミュレータを起動します。
コマンドラインでプロジェクトのルート ディレクトリに移動し、次のコマンドを入力します。
$ flutter drive \
--driver=test_driver/perf_driver.dart \
--target=integration_test/perf_test.dart \
--profile \
--no-dds
すべてが問題なく動作した場合は、次のような出力が表示されます。
Running "flutter pub get" in testing_app...
Resolving dependencies...
archive 3.3.2 (3.3.6 available)
collection 1.17.0 (1.17.1 available)
js 0.6.5 (0.6.7 available)
matcher 0.12.13 (0.12.14 available)
meta 1.8.0 (1.9.0 available)
path 1.8.2 (1.8.3 available)
test 1.22.0 (1.23.0 available)
test_api 0.4.16 (0.4.18 available)
test_core 0.4.20 (0.4.23 available)
vm_service 9.4.0 (11.0.1 available)
webdriver 3.0.1 (3.0.2 available)
Got dependencies!
Running Gradle task 'assembleProfile'... 1,379ms
✓ Built build/app/outputs/flutter-apk/app-profile.apk (14.9MB).
Installing build/app/outputs/flutter-apk/app-profile.apk... 222ms
I/flutter ( 6125): 00:04 +1: Testing App Performance (tearDownAll)
I/flutter ( 6125): 00:04 +2: All tests passed!
All tests passed.
テストが正常に完了した後には、プロジェクトのルートにある build ディレクトリに次の 2 つのファイルがあります。
scrolling_summary.timeline_summary.json
には、サマリーが入っています。テキスト エディタで開いて、含まれている情報を確認してください。scrolling_summary.timeline.json
完全なタイムライン データが入っています。
統合テストの詳細については、以下にアクセスしてください。
9. 演習の完了
この Codelab を修了し、Flutter アプリをテストするためのさまざまな方法を学びました。
学習した内容
- 単体テストを使用してプロバイダをテストする方法
- ウィジェット テスト フレームワークを使用してウィジェットをテストする方法
- 統合テストを使用してアプリの UI をテストする方法
- 統合テストを使用してアプリの性能をテストする方法
Flutter でのテストについて詳しくは、以下をご覧ください。