アプリを3本リリースしましたが、テストを1回も書いていない・・・
とりあえずユニットテストのやり方ぐらいは抑えておこうと思って最低限の動作確認を行いました。
テストプロジェクト作成
Flutter:2.2.1
Dart:2.13.1
なので、Null safetyが有効になっています。
>flutter create unit_test
パッケージの追加
flutter_testパッケージは、flutter create すると最初から入ってました。
httpパッケージは、ユニットテストとは直接関係ないのですが、テストの種類を増やすために追加しています。
# Null safety有効
environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
http: ^0.13.3 # 追加
dev_dependencies:
flutter_test: # 最初から入っている
sdk: flutter
テスト対象となるCounterクラスの作成
みんな大好きカウンター。
// lib/counter.dart
class Counter {
int value = 0;
void increment() => value++;
void decrement() => value--;
}
ユニットテストを書いてみる
ソース全文は最後に載せるとして、いくつかのトピックを紹介します。
Null safety有効時の編集の初期化について
lateをつけて、宣言後に初期化されるnon-nullable編集であることを宣言しておく必要があるようです。あんまり詳しくわかっていませんがこれがないとコンパイル通りません。
void main() {
late var httpClient;
setUp()、tearDown()について
各テストの実行前と実行後に呼ばれます。
テスト失敗していてもtearDown()は呼ばれます。
それぞれに適切な処理を入れて試したかったので、httpパッケージを追加した感じです。
setUp(() async {
// 各テストの実行前に呼ばれる。
httpClient = http.Client();
});
tearDown(() async {
// 各テストの実行後に成否に関わらず呼ばれる。
httpClient.close();
});
test('HTTP GETで正常のページを取得できること', () async {
var response = await httpClient.get(Uri.parse('https://note.com/take4_24'));
expect(response.statusCode, equals(200));
});
テストグループ
テストをグルーピングすることができます。グループの説明は、テストの説明の前に追加されて、テスト結果が出力されます。
group('Counterに関するテストグループ', () {
test('value should start at 0', () {
expect(Counter().value, equals(0));
});
test('value should be incremented', () {
final counter = Counter();
counter.increment();
expect(counter.value, equals(1));
});
こんな感じ。

色々な評価
例によって評価関数はたくさんあるようなので少しずつ覚えていこうと思います。
equals()でラップする理由はよくわかりませんでした。別になくてもテストは成功しますが、サンプルに書いてあったので一応書いてます。誰か教えてください。。

expect(counter.value, equals(1));
expect(string.split(','), equals(['foo', 'bar', 'baz']));
expect(
'foo,bar,baz',
allOf([
contains('foo'),
isNot(startsWith('bar')),
endsWith('baz'),
]));
expect(() => int.parse('X'), throwsFormatException);
ユニットテスト実行方法
Visual Studio Codeの場合です。
ユニットテストファイル(今回の場合test/counter_test.dart)を開いた状態でMenu>Run>Start Debuggingでテスト実行できます。

エディタに表示される「Run」で個別実行もできます。

とりあえず大体わかったので、あとは実際のアプリでユニットテスト書いて見ようと思います。
ソース全文
// lib/counter_test.dart
// Import the test package and Counter class
import 'package:flutter_test/flutter_test.dart';
import 'package:unit_test/counter.dart';
import 'package:http/http.dart' as http;
void main() {
// Null Safety有効時は、lateをつける。
// 「宣言後に初期化されるnon-nullable変数の宣言」に当たる。
late var httpClient;
setUp(() async {
// 各テストの実行前に呼ばれる。
httpClient = http.Client();
});
tearDown(() async {
// 各テストの実行後に成否に関わらず呼ばれる。
httpClient.close();
});
test('HTTP GETで正常のページを取得できること', () async {
var response = await httpClient.get(Uri.parse('https://note.com/take4_24'));
expect(response.statusCode, equals(200));
});
group('Counterに関するテストグループ', () {
test('value should start at 0', () {
expect(Counter().value, equals(0));
});
test('value should be incremented', () {
final counter = Counter();
counter.increment();
expect(counter.value, equals(1));
});
test('value should be decremented', () {
final counter = Counter();
counter.decrement();
expect(counter.value, equals(-1));
});
});
test('String.split() splits the string on the delimiter', () {
var string = 'foo,bar,baz';
//expect(string.split(','), equals(['foo', 'bar', 'baz']));
expect(string.split(','), equals(['foo', 'bar', 'baz']));
});
test('String.trim() removes surrounding whitespace', () {
var string = ' foo ';
expect(string.trim(), equals('foo'));
});
group('文字列処理に関するテストグループ', () {
test('.split() splits the string on the delimiter', () {
var string = 'foo,bar,baz';
expect(string.split(','), equals(['foo', 'bar', 'baz']));
});
test('.trim() removes surrounding whitespace', () {
var string = ' foo ';
expect(string.trim(), equals('foo'));
});
});
group('intに関するテストグループ', () {
test('.remainder() returns the remainder of division', () {
expect(11.remainder(3), equals(2));
});
test('.toRadixString() returns a hex string', () {
expect(11.toRadixString(16), equals('b'));
});
});
test('複雑な検証を行う', () {
expect(
'foo,bar,baz',
allOf([
contains('foo'),
isNot(startsWith('bar')),
endsWith('baz'),
]));
});
test('Exceptionが起こること', () {
expect(() => int.parse('X'), throwsFormatException);
});
}
参考にしたサイト



宣伝
この記事が役になったら拙作アプリをぜひお試しください。
コメント