🧪

【Flutter】ユニットテストについて学ぶ

2023/04/20に公開

https://docs.flutter.dev/cookbook/testing/unit/introduction
上記の公式チュートリアルについて、書いてる通りに実行すれば簡単なユニットテストが数行で実行できる。

ユニットテストの基本構成は以下。
test関数の引数に、「test名」と「テストしたい処理」を書いていく。
expect関数で、「テスト対象の変数やオブジェクト」と「期待する値や状態」を比較する。

以下のテストコード例だと、足し算の結果が5になるかをテストしており、テストを実行するともちろん成功となる。

test('テスト名', () {
  // テストしたい処理
  int result = 2 + 3;

  expect(result, 5); //test OK
});

 
基本的なテスト方法は記載の通りだが、それだとあっさりしすぎなので、ここではもう少し深掘りしてユニットテストについて確認していく。

 
 
 

test()

pub.dev/test function

必須のプロパティは、以下の2つ。
他にもプロパティは存在するが、基本的にはデフォルトの設定で問題無し。

description: テストの名前
body: テストしたい処理

オプションでプロパティの設定が必要な場合は、以下参照。
 

【各オプションプロパティの詳細】

testOn:

このテストが実行される環境を指定する文字列。
例えば、特定のOSやブラウザでのみ実行するテストを定義する場合に使用する。
pub.dev/test/Platform Selectors

timeout:

テストが完了するまでに許される最大時間。
Durationオブジェクトで指定。

skip:

テストをスキップするかどうかの制御。
"true"または"文字列"を指定すると、テストがスキップされ、
"文字列"を指定した場合は、記載した文字列が表示される。(スキップ理由などを文字列で記載)

"false"または指定しない場合は、テストが実行される。

tags:

テストにタグを割り当てれる。
テストの実行をフィルタリングする際に使用。
注)tagsを使用するには、プロジェクトルートにdart_test.yamlファイルを作成し、タグを定義する必要あり。

dart_test.yaml
tags:
  タグ名:

onPlatform:

特定のプラットフォームでテストを実行する際に、タイムアウトやスキップの設定を上書きするために使用。
プラットフォーム名をキーとし、タイムアウトやスキップを値とするマップを指定。

retry:

テストが失敗した場合、再試行する回数を指定。
デフォルトでは再試行は行わない。

【オプションのプロパティ全盛りのテスト例】

test('足し算テスト', () {
    int result = 2 + 3;
    expect(result, 5);
  },
  testOn: 'mac-os', // このテストはmac-osでのみ実行される。
  timeout: Timeout(Duration(seconds: 2)), // テストが2秒以上かかった場合はテスト失敗。
  skip: false, // テストをスキップしない。
  tags: 'math', // このテストにmathタグを追加。
  onPlatform: {
    // 特定のプラットフォームでテストのタイムアウトを上書きする設定。
    'windows': Timeout(Duration(seconds: 3)),
    'linux': Timeout(Duration(seconds: 1)),
  },
  retry: 2, // テストが失敗した場合、2回まで再試行する。
);

 
 
 

expect()

pub.dev/expect function

必須のプロパティは以下の2つ。

actual: テスト対象の変数やオブジェクト
matcher: 期待される値や状態を表すMatcherオブジェクト

"matcher"を使用して、テスト対象の変数やオブジェクトが期待通りの振る舞いをしているかどうかを検証することができる。

"matcher"には"値"を指定することができ、その場合は "equals matcher"でラップされる。

expect(result, 5);
// ↓ 上記のように記載すると、内部的にラップされる
expect(result, equals(5));

オプションでプロパティの設定が必要な場合は、以下参照。
 

【各オプションプロパティの詳細】

reason:

公式に記載があるように、通常は省略される。
指定すると、テスト失敗時に表示されるメッセージを指定できる。

This is the main assertion function.
reason is optional and is typically not supplied, as a reason is generated from matcher;
if reason is included it is appended to the reason generated by the matcher.

skip:

test()と一緒で、テストをスキップするかどうかの制御。
"true"または"文字列"を指定すると、テストがスキップされ、
"文字列"を指定した場合は、記載した文字列が表示される。(スキップ理由などを文字列で記載)

"false"または指定しない場合は、テストが実行される。

 

【matcherとは?】

"matcher"とは、下記のような意味合いです。

"特定のパターンや条件に一致するデータを見つけるために使用されるアルゴリズムやツールのこと。"

"テストで期待される値や条件を表現するオブジェクトのこと。"

使用できるmatcher一覧は以下の公式参照。
pub.dev/matcher library

【matcherの使用例】

void main() {
  test('equals sample', () {
    int actual = 5;
    int expected = 5;
    expect(actual, equals(expected));
  });

  test('contains sample', () {
    List<int> numbers = [1, 2, 3, 4, 5];
    int value = 3;
    expect(numbers, contains(value));
  });

  test('isEmpty sample', () {
    List<int> emptyList = [];
    expect(emptyList, isEmpty);
  });
}

 
 
 

test()と併用できるオプションの関数

オプションでtest関数と組み合わせて使用できる関数は、以下のような関数がある。

group():

関連するテストをグループ化し、テストの出力を整理して可読性が向上される。
また、共通のsetUp()やtearDown()関数を適用するために使用。

setUp():

テストの実行前に実行される共通の処理を記述するために使用。
例えば、オブジェクトの初期化やデータの設定など。

setUpAll():

setUp()と同様にテストの実行前に実行されるが、setUpAll()はテストグループ内のすべてのテストの開始前に1回だけ実行される。
大量のデータの準備や一度だけ行いたい処理がある場合に使用。

tearDown():

テストの実行後に実行される共通の処理を記述するために使用。
例えば、オブジェクトの破棄やリソースの解放など。

tearDownAll():

tearDown()と同様にテストの実行後に実行されるが、tearDownAll()はテストグループ内のすべてのテストの終了後に1回だけ実行される。
大量のデータのクリーンアップや一度だけ行いたい場合に使用。

【オプション関数全盛りのテスト例】

print文を入れて、実行タイミングを出力結果から確認する。

void main() {
  print('main start');

  // 全テスト開始前に1回だけ実行される
  setUpAll(() {
    print('main setUpAll');
  });

  // 各テストケースの前に実行される
  setUp(() {
    print('main setUp');
  });

  // 各テストケースの後に実行される
  tearDown(() {
    print('main tearDown');
  });

  // 全テスト終了後に1回だけ実行される
  tearDownAll(() {
    print('main tearDownAll');
  });

  group('group1', () {
    print('group1 start');

    // グループ内の全テスト開始前に1回だけ実行される
    setUpAll(() {
      print('group1 setUpAll');
    });

    // グループ内の各テストケースの前に実行される
    setUp(() {
      print('group1 setUp');
    });

    // グループ内の各テストケースの後に実行される
    tearDown(() {
      print('group1 tearDown');
    });

    // グループ内の全テスト終了後に1回だけ実行される
    tearDownAll(() {
      print('group1 tearDownAll');
    });

    test('test1', () {
      print('test1');
    });

    test('test2', () {
      print('test2');
    });

    // 簡単な足し算テスト
    test('2 + 3 = 5', () {
      int result = 2 + 3;
      expect(result, equals(5));
    });

    print('group1 end');
  });

  print('main end');
}

【実行結果】

main start
group1 start
group1 end
main end

main setUpAll
group1 setUpAll
main setUp
group1 setUp
main test1
group1 tearDown
main tearDown
main setUp
group1 setUp
main test2
group1 tearDown
main tearDown
main setUp
group1 setUp
2 + 3 = 5
group1 tearDown
main tearDown
group1 tearDownAll
main tearDownAll

 
 
 

おわり

別の記事では、公式チュートリアルのモックを使ったユニットテストについても深堀りしてみましたので、よければこちらもご覧ください!

https://zenn.dev/ncdc/articles/flutter_unit_test_mock

GitHubで編集を提案
NCDCエンジニアブログ

Discussion