Flutterで単体テストを書く

以前[こちら(TODOアプリで比較するAndroid(MVVM)とFlutter(Provider)の違い)](https://qiita.com/tokkun5552/items/8e368bdf0c28badcf535)の記事で紹介した、TODOアプリの単体テストを書いてみました。
やってみてわかったことを共有します。ここはこうすれば解決する！とかこうした方がいい！とかあればコメントくださると嬉しいです。

#### 筆者のレベル
- Flutterの実務経験なし（やり始めて半年未満）
- 普段はSIerでインフラエンジニア
- インフラ周りといってもオンプレミスしかしらない・・・

# やったこと
以前私が書いたTODOAppSample-Flutterで単体テストを行いました。
[全コードはこちら](https://github.com/tokku5552/TODOAppSample-Flutter/tree/v1.1)

基本的には以下の公式ドキュメントにやり方が書いてありますのでご参照ください。
[An introduction to unit testing](https://flutter.dev/docs/cookbook/testing/unit/introduction)

#### 必要な準備
``pubspec.yaml``に以下のように``flutter_test``が追加されている必要があります。
![スクリーンショット 2020-12-14 181511.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141823/029b7036-847c-f6bd-92d3-1fe64fc866f2.png)
また、私は以下のような環境で実施しました。
OS:Windows 10 Pro 1909
IDE:Android Studio 4.1.1

# コード
テストコードですが、プロジェクトルートのtestフォルダの下に、テストしたいdartファイルの名前に``_test``とつけたファイルを作成してその中に書きます。
![スクリーンショット 2020-12-14 181959.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141823/4141564b-f76a-8e3c-7878-e21364f77f98.png)

基本的な単体テストは簡単にかけて、以下のようになります。

```dart:test.dart

import 'package:flutter_test/flutter_test.dart';
import 'package:todo_app_sample_flutter/data/todo_item.dart';

void main() {
  group("TodoItemのゲッターのテスト", () {
    final TodoItem todoItem = TodoItem(
      id: 0,
      title: 'title',
      body: 'body',
      createdAt: DateTime(2020, 1, 1),
      updatedAt: DateTime(2020, 1, 1),
      isDone: true,
    );

    test('idのテスト', () {
      expect(todoItem.getId, 0);
    });
```

``flutter_test.dart``とテスト対象のdartファイルをimportします。
``main()``の中に``test()``関数を書き、その中で期待する値を``expect(<テスト対象が出力する値>,<期待される結果>)``のように書きます。
``test()``が一つのテストケースに対応している感じです。
各``test()``を``group()``でグループ化することもできます。

[全テストコードはこちら](https://github.com/tokku5552/TODOAppSample-Flutter/blob/v1.1/test/todo_item_test.dart)

# テスト実行
実行するには以下のようにテストファイル上で右クリックし、**実行**⇒**test in ~~~**　をクリックします。
![スクリーンショット 2020-12-14 183037.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141823/f2903428-9252-6f60-d421-8823ddf8640b.png)

テストが流れて結果が画面下部に表示されます。
緑のチェックマークがついていればテストにpassしているということになります。
![スクリーンショット 2020-12-14 183316.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141823/14dbdb3c-b9e8-e7a9-75ce-da51fbb6e4a6.png)

テストに失敗すると、エラーと共に以下のように出力されます。
![スクリーンショット 2020-12-14 183434.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141823/7a83ee2f-f50f-4723-185d-bf14d5ce90a0.png)
上記の例では期待される結果の日付をわざと変えました。
``Expected``と``Actual``が違うということを示してくれます。

# 結論
この記事のために書いたテストは非常に簡単にかけました。なぜなら**TodoItemはデータを格納しているだけのエンティティの役割しかない**からです。
しかし、他のクラスに対してテストを書こうとすると途端に書けないことに気づきました。


他のclassに対してもテストを行おうとしましたが、私が書いたコードではテストできません。



```dart:todo_item_repository.dart
class TodoItemRepository {
  static String table = 'todo_item';
  static DatabaseProvider instance = DatabaseProvider.instance;

```

DBのインスタンスを内部で生成してしまっているため、mockに置き換えられないためです。

こういうことを起こさないために、インスタンスの生成時にDBのインスタンスを外から渡してあげればよいようです。
個人開発している分にはコアとなるビジネスロジックだけ依存関係に注意しながらコーディングしておいて、テストしやすいようにしておくのが良いと思いました。
そのうちテスタブルなコードに書き換えてまた記事にしようと思います。

#### 2021/02/15 追記
記事にしました！[【Flutter】Providerで最低限のDIを行ってテスタブルなコードにリファクタリングする](https://qiita.com/tokkun5552/items/7af34769104e94f50745)

#### 2021/4/9 追記
こちらの記事に関する発表を行いました。
以下が発表スライドです。
<iframe src="//www.slideshare.net/slideshow/embed_code/key/sYJVD9J7Ve7IaL" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/ShinnosukeTokuda/fluttergithub-actions" title="Flutterで単体テストを行う方法とGitHub Actionsを使った自動化" target="_blank">Flutterで単体テストを行う方法とGitHub Actionsを使った自動化</a> </strong> from <strong><a href="https://www.slideshare.net/ShinnosukeTokuda" target="_blank">ShinnosukeTokuda</a></strong> </div>