Flutter testとDDDの組み合わせで快適なFlutter Lifeを

みなさん、Flutter life楽しんでいますか？僕は、ゼミが終わって絶賛楽しんでます！！笑
この記事では、ドメイン駆動設計(以下、DDD)でFlutterのテストをどのように設計、実装したのかについてお話しします！
# お品書き

1. この記事を書こうと思ったきっかけ
2. 設計&実装方法　〜DDDを添えて〜
3. テストを設計設計する上で何を意識したか？
4. まとめ
5. 参考記事

# この記事を書こうと思ったきっかけ
Flutterは爆速でプロダクト(スマホアプリ)を開発するには非常に最適なフレームワークです。また、初心者でもとっつきやすい仕様になっており、誰でも簡単にある程度なら実装できるようになっています。しかし、それが仇となって、現在様々なプロジェクトにおいて弊害が生じているとよく耳にします。

特に、自分は**プロダクトの保守性が担保されないこと**という大きな課題があると考えています。

具体的に挙げると、チームの中で初心者と上級者が混じることでコードの書き方がバラバラになってしまうことや、それにより開発速度の低下やコード同士の依存関係の増幅、不明なバグが生じるなど様々あります。そして、このような状況では綺麗にテストが書けません。。。

そこで、本記事では依存を極力なくす形にするためにDDDを採用したテストの方針を決めて実装する方法を紹介します。

# 設計&実装方法

一例となるプロジェクトのディレクトリ構造は以下の通りとなっています。

```bash
lib
  └─repositories # DBと直接接続する唯一の層
  └─view_models # UIとrepository, service層の架け橋
  └─services # repositoryの責務以外のロジックがまとまった層
  └─views # UI層
	...
```

それぞれ実行したいファイルごとに mock を作成します。
mock は自動生成されるので**生成したいファイルだけ指定すればいい感じに生成してくれます。**
また、一つの層に対して一つのファイルでその層の mock を管理します。
mock ファイルのディレクトリ構造は以下となっています。



```bash
# 
lib
	└─mock
		  └─repository
				  └─repository.dart # 生成したいクラスを指定するファイル
				  └─repository.mocks.dart # 自動生成後のファイル
		  └─service
```

具体的な記述方法は以下の通りです。

```dart
// mock生成ファイル(repository.dart)
import 'package:mockito/annotations.dart';

import 'package:artisan_app/repositories/auth/auth_repository.dart';
import 'package:artisan_app/repositories/daily_report/daily_report_repository.dart';

// 複数mockを指定することができます
// @GenerateNiceMocks([MockSpec<生成したいクラス名>()])
@GenerateNiceMocks([MockSpec<AuthRepository>()])
@GenerateNiceMocks([MockSpec<DailyReportRepository>()])
// ここのimportの記述は必須です
import 'repository.mocks.dart';
```

その後、自動生成するために以下のコマンドを実行します。

```bash
flutter pub run build_runner build --delete-conflicting-outputs
```

テストに関するディレクトリ構造は、mock層と同様に各層ごとにフォルダを作成します。

```bash
lib
  └─test
	 └─repository
     └─service
```

テストコードの記述方法は、以下の通りです。
`※あくまで一例なため、その時に必要なテスト方法で実行してください。`

```dart
// testファイル
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';

// testしたいmockじゃないクラスを指定する
@GenerateMocks([AuthRepository])
void main() {
  // testの内容はよしなに書いていきましょう！！！
  test('unit Test', () {
    var test = 5;
    expect(test, 5);
  });
}
```

実行方法は以下の通りです。

```bash
flutter test test/repository/auth_repository.test.dart
```

# テストを設計する上で何を意識したか？

主に意識したことは、

1. **各層に対応した**テストをかいた
2. 優先順位を決めた
3. **mockをばらけないように一か所に集約した**

ことです。具体的に説明します。

1. 今回はDDDを採用していますが各プロジェクトによって採用するアーキテクチャは異なるのでどんな層の分け方をしても対応できるような形に設計しました。

2. 今回の事例ではすでに開発が進んでおりファイル量が非常に多い状態でした。そのため、絶対にテストが必要なview_model、そしてrepository, serviceと分けました。さらに、view_modelの中でも、次のリリースまでにテストの準備した方がいいものを分別して行いました。今回の設計だと問題なくその流れでもテストを実装することができました。

3. mockを一つの箇所に集約させたことにより、バラバラになったモックが整理されていない状況にならずに済みました。

# 最後に

ここまでFlutterのテストをDDDで行う場合についてお話ししていきましたが、いかがでしたか？

この記事ではテストの具体的な書き方については触れませんでしたが、おそらくproviderやriverpodを使用すると少しややこしい書き方になるので、これに関しては別途記事を書きたいと思います。

この記事が誰かのお役に立てましたら非常に幸いです！
以下が参考記事です。

# 参考記事

[【Flutter/Dart】 testについて](https://zenn.dev/tomofyro/articles/1228cf8f2b02ce)

[Flutter RiverpodでDIしたクラスをMockitoでモック化してUnitTestを書く | ZUMA Lab](https://zuma-lab.com/posts/flutter-mockito-di-unit-test)

[Mock dependencies using Mockito](https://docs.flutter.dev/cookbook/testing/unit/mocking)

[Mockitoを使ってDartでのTDDを加速させよう - Uzabase for Engineers](https://tech.uzabase.com/entry/2020/03/24/182505)

[An introduction to unit testing](https://docs.flutter.dev/cookbook/testing/unit/introduction)

[Mock dependencies using Mockito](https://docs.flutter.dev/cookbook/testing/unit/mocking)

[mockito](https://pub.dev/documentation/mockito/latest/)

[https://github.com/dart-lang/mockito](https://github.com/dart-lang/mockito)

[Flutter での単体テスト（完全版） - Qiita](https://qiita.com/ko2ic/items/78c4a035a0aef9cfc78d#repository%E3%81%AA%E3%81%A9%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88)

[Flutter の Null safety に対応した Mockito の基本的な使い方 | ZUMA Lab](https://zuma-lab.com/posts/flutter-mockito-null-safety-unit-test)

[Makefile で第 2 引数を使う方法 - Qiita](https://qiita.com/algas/items/499d0d69d51a1cc7639f#make%E5%A4%89%E6%95%B0%E3%82%84%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E3%82%92%E4%BD%BF%E3%81%86)