Skip to content

Commit

Permalink
test: Add initial integration tests
Browse files Browse the repository at this point in the history
Refer to `docs/integration_tests.md` for
notes.
  • Loading branch information
sirpengi committed Oct 11, 2023
1 parent c990712 commit 9e660b5
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 14 deletions.
54 changes: 54 additions & 0 deletions docs/integration_tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Integration Tests

Integration tests in flutter allow you to run test code
on physical devices. These tests also allow performance
metrics to be captured for analysis, and more accurately
reflect production performance as it runs on real devices.

For more background see
[Flutter docs on integration testing][flutter-docs]

[flutter-docs]: https://docs.flutter.dev/cookbook/testing/integration/profiling

## Writing tests

Writing an integration test is very much similar to
writing widget tests: you have the same access to the
`testWidgets` function as well as a `WidgetTester` to
set up and run interactions.

A big difference is the usage of an
`IntegrationTestWidgetsFlutterBinding` binding
that has a `traceAction` method that will capture
performance metrics like widget build times as well
as rendering times.

These integration tests are recommended to be written
in `integration_tests/`.

The integration tests also interact with test driver
code, which a sample exists as `test_driver/perf_driver.dart`.

This test driver grabs performance metrics obtained
by a `traceAction` call with a `reportKey: test` and
writes both the entire timeline as well as a summary
to disk inside the `build` directory.

## Running tests

The command to run an integration test:

```
$ flutter drive \
--driver=test_driver/perf_driver.dart \
--target=integration_test/unreadmarker_test.dart \
-d <device_id> \
--profile \
--no-dds
```

Obtain the `device_id` using `flutter devices`.

Note: the `--no-dds` flag is required for running tests
on mobile devices or simulators. It could be removed
for other contexts.
82 changes: 82 additions & 0 deletions integration_test/unreadmarker_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:zulip/api/model/events.dart';
import 'package:zulip/api/model/model.dart';
import 'package:zulip/model/narrow.dart';
import 'package:zulip/model/store.dart';
import 'package:zulip/widgets/message_list.dart';
import 'package:zulip/widgets/store.dart';

import '../test/api/fake_api.dart';
import '../test/model/binding.dart';
import '../test/example_data.dart' as eg;
import '../test/model/message_list_test.dart';
import '../test/model/test_store.dart';

void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
TestZulipBinding.ensureInitialized();

late PerAccountStore store;
late FakeApiConnection connection;

Future<List<Message>> setupMessageListPage(WidgetTester tester, {
Narrow narrow = const AllMessagesNarrow(),
bool foundOldest = true,
int? messageCount,
List<Message>? messages,
}) async {
addTearDown(testBinding.reset);
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
connection = store.connection as FakeApiConnection;

// prepare message list data
store.addUser(eg.selfUser);
assert((messageCount == null) != (messages == null));
messages ??= List.generate(messageCount!, (index) {
return eg.streamMessage(id: index, sender: eg.selfUser, flags: [MessageFlag.read]);
});
connection.prepare(json:
newestResult(foundOldest: foundOldest, messages: messages).toJson());

await tester.pumpWidget(
MaterialApp(
home: GlobalStoreWidget(
child: PerAccountStoreWidget(
accountId: eg.selfAccount.id,
child: MessageListPage(narrow: narrow)))));
await tester.pumpAndSettle();
return messages;
}

testWidgets('Perf test', (tester) async {
final messages = await setupMessageListPage(tester, messageCount: 500);
final messageIds = messages.map((e) => e.id).toList();
await tester.pump(const Duration(seconds: 2));

await binding.traceAction(
() async {
store.handleEvent(UpdateMessageFlagsRemoveEvent(
id: 1,
flag: MessageFlag.read,
messages: messageIds,
messageDetails: {},
));
await tester.pumpAndSettle();
await tester.pumpAndSettle();
store.handleEvent(UpdateMessageFlagsAddEvent(
id: 2,
flag: MessageFlag.read,
messages: messageIds,
all: true,
));
await tester.pumpAndSettle();
await tester.pumpAndSettle();
},
reportKey: 'test',
);
});
}
65 changes: 52 additions & 13 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ packages:
dependency: "direct main"
description:
name: app_settings
sha256: "67ca58aba6ec311d89597c2716d2e37da54b8c7cef28b7749e6551c57f88c1f4"
sha256: "09bc7fe0313a507087bec1a3baf555f0576e816a760cbb31813a88890a09d9e5"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
version: "5.1.1"
args:
dependency: transitive
description:
Expand Down Expand Up @@ -293,10 +293,10 @@ packages:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
version: "6.1.4"
file_picker:
dependency: "direct main"
description:
Expand Down Expand Up @@ -350,6 +350,11 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_driver:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
Expand Down Expand Up @@ -389,6 +394,11 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.2.0"
fuchsia_remote_debug_protocol:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
glob:
dependency: transitive
description:
Expand Down Expand Up @@ -501,6 +511,11 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.1"
integration_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
intl:
dependency: "direct main"
description:
Expand Down Expand Up @@ -577,10 +592,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
mime:
dependency: transitive
description:
Expand Down Expand Up @@ -681,10 +696,10 @@ packages:
dependency: transitive
description:
name: platform
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.1.2"
plugin_platform_interface:
dependency: transitive
description:
Expand All @@ -701,6 +716,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.1"
process:
dependency: transitive
description:
name: process
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
url: "https://pub.dev"
source: hosted
version: "4.2.4"
pub_semver:
dependency: transitive
description:
Expand Down Expand Up @@ -874,6 +897,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
sync_http:
dependency: transitive
description:
name: sync_http
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
term_glyph:
dependency: transitive
description:
Expand Down Expand Up @@ -1006,10 +1037,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "0fae432c85c4ea880b33b497d32824b97795b04cdaa74d270219572a1f50268d"
sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583
url: "https://pub.dev"
source: hosted
version: "11.9.0"
version: "11.10.0"
watcher:
dependency: transitive
description:
Expand All @@ -1022,10 +1053,10 @@ packages:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
sha256: "14f1f70c51119012600c5f1f60ca68efda5a9b6077748163c6af2893ec5df8fc"
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
version: "0.2.1-beta"
web_socket_channel:
dependency: transitive
description:
Expand All @@ -1034,6 +1065,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.0"
webdriver:
dependency: transitive
description:
name: webdriver
sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
webkit_inspection_protocol:
dependency: transitive
description:
Expand Down Expand Up @@ -1075,5 +1114,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.2.0-73.0.dev <4.0.0"
dart: ">=3.2.0-157.0.dev <4.0.0"
flutter: ">=3.14.0-5.0.pre.23"
6 changes: 5 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ dependencies:
path_provider: ^2.0.13
path: ^1.8.3
sqlite3_flutter_libs: ^0.5.13
app_settings: ^5.0.0
app_settings: ^5.1.1
image_picker: ^1.0.0
package_info_plus: ^4.0.1
collection: ^1.17.2
Expand All @@ -61,8 +61,12 @@ dependencies:
sdk: flutter

dev_dependencies:
flutter_driver:
sdk: flutter
flutter_test:
sdk: flutter
integration_test:
sdk: flutter

# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
Expand Down
27 changes: 27 additions & 0 deletions test_driver/perf_driver.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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['test']);

// Convert the Timeline into a TimelineSummary that's easier to
// read and understand.
final summary = driver.TimelineSummary.summarize(timeline);

// Then, write the entire timeline to disk in a json format.
// This file can be opened in the Chrome browser's tracing tools
// found by navigating to chrome://tracing.
// Optionally, save the summary to disk by setting includeSummary
// to true
await summary.writeTimelineToFile(
'trace_output',
pretty: true,
includeSummary: true,
);
}
},
);
}

0 comments on commit 9e660b5

Please sign in to comment.