FlutterでClean ArchitectureとTDDをやってみる その2 - EntityとUseCase

- [Flutter TDD Clean Architecture Course 2](https://resocoder.com/2019/08/29/flutter-tdd-clean-architecture-course-2-entities-use-cases/) を解説します。
- コードは該当ページのGitHubリンクから取得するのが良いと思います。
- チュートリアルを読むのが早いと思いますが、その前に[概要をこちらにまとめた](https://qiita.com/maria_mari/items/8fdd92af13a36c663360)ので呼んでいただいてもいいかなと思います。

---

# どこからコーディングを始めるか
- コーディングは内側から始める
    - 最も変化しないDomain層のEntityから始める

# Entity
- Number Triviaアプリが扱うデータは**NumberTrivia** entitiyである。
    - [NumberTrivia API](http://numbersapi.com/#42)からのレスポンスを基に作成する
        - レスポンスの例を[http://numbersapi.com/#42](http://numbersapi.com/#42)から得る。
            - String text と int number をプロパティとする
        - not_foundのレスポンスを[http://numbersapi.com/123456?json](http://numbersapi.com/123456?json)から得る
- EntityはTestしない
    - 自明であるし変化しないためだと思われる
- 値の比較のためにEquatableを継承する

# Use Cases
- ユースケースではビジネスロジックが実行される
    - Repositoryからデータを得る
- このアプリでは2つのユースケースを用いる
    - GetConcreteNumberTrivia
    - GetRandomNumberTrivia

## データフローとエラーハンドリング
- UseCaseとRepositoryで返す2つの型
    - NumberTrivia Entity
        - UseCaseはRepositoryからNumberTrivia Entityを受け取り、Presentation層に渡す
    - Error
        - できるだけ早い段階（Repositoryの段階）で例外をcatchしてFailureオブジェクトを返す方法をとる

- Either: EntityとFailureの2つの型を扱う方法
    - dartz packageを利用する
- Repositoryが返すデータ型は` Future<Either<Failure, NumberTrivia>>`となる

# Repository Contract
- Use CasesはRepositoryからデータを取得する
    - Repositoryはdomain層とdata層に位置する
        - Contract(インターフェイス)がdomain層に位置する
        - 実装がdata層に位置する
        - これにより依存性逆転しdomain層の独立が保てる
        - また、testabilityが得られる
        - testabilityと分離が良い設計となる
- Repositoryのインターフェイスのみ作成する。

```number_trivia_repository.dart
import 'package:dartz/dartz.dart';

import '../../../../core/error/failure.dart';
import '../entities/number_trivia.dart';

abstract class NumberTriviaRepository {
  Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int number);
  Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia();
}
```

## モックによるテスト
- 実装がないクラスを用いるテストにはモック使う
    - mockitoパッケージを使う

# TDD
- 先にテストを書く
    - `/test/usecases/get_concrete_number_trivia_test.dart` を作成する。これがテストファイルになる
    - `/lib/domain/usecases/get_concrete_number_trivia.dart` を作成する

## テストを作成して失敗させる
- この段階でUseCaseで用いるRepositoryのインターフェイスは作成済みであるが、実装はまだ作成していない
    - Repositoryのインターフェイスを継承するmockを使いテストする
- 以下のテストは`GetConcreteNumberTrivia`がないため失敗する

```get_concrete_number_trivia_test.dart
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/repositories/number_trivia_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

// NumberTriviaRepository（インターフェイス）を用いてmockを作成する
class MockNumberTriviaRepository extends Mock
    implements NumberTriviaRepository {}

void main() {
  // GetConcreteNumberTrivia は存在しないためErrorとなる
  GetConcreteNumberTrivia usecase;
  MockNumberTriviaRepository mockNumberTriviaRepository;

  setUp(() {
    mockNumberTriviaRepository = MockNumberTriviaRepository();
    usecase = GetConcreteNumberTrivia(mockNumberTriviaRepository);
  });
}
```

## 失敗したテストを成功させるためにusecaseを作成する

```get_concrete_number_trivia.dart
import '../repositories/number_trivia_repository.dart';

class GetConcreteNumberTrivia {
  final NumberTriviaRepository repository;

  GetConcreteNumberTrivia(this.repository);
}
```

## テスト本体を書く
```get_concrete_number_trivia_test.dart
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/entities/number_trivia.dart';
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/repositories/number_trivia_repository.dart';
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/usecases/get_concrete_number_trivia.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

class MockNumberTriviaRepository extends Mock
    implements NumberTriviaRepository {}

void main() {
  GetConcreteNumberTrivia usecase;
  MockNumberTriviaRepository mockNumberTriviaRepository;

  setUp(() {
    mockNumberTriviaRepository = MockNumberTriviaRepository();
    usecase = GetConcreteNumberTrivia(mockNumberTriviaRepository);
  });

  final tNumber = 1;
  final tNumberTrivia = NumberTrivia(number: 1, text: 'test');

  test(
    'should get trivia for the number from the repository',
    () async {
      // Mockを用いたテスト
      // getConcreteNumberTrivia が呼ばれた際は常にRight（=成功）側のNumberTriviaオブジェクトを返すようにする
      when(mockNumberTriviaRepository.getConcreteNumberTrivia(any))
          .thenAnswer((_) async => Right(tNumberTrivia));
      // テストの実行のコード。まだ書いていないメソッドに対してテストをする = 失敗させる
      final result = await usecase.execute(number: tNumber);
      // UseCaseは単純にRepositoryが返す値を返す
      expect(result, Right(tNumberTrivia));
      // Respositoryでメソッドが呼ばれたことを検証する
      verify(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
      // Respositoryでメソッドがそれ以降呼ばれないことを検証する
      verifyNoMoreInteractions(mockNumberTriviaRepository);
    },
  );
}
```

## テストが成功するような実装を書く

```get_concrete_number_trivia.dart
import 'package:dartz/dartz.dart';
import 'package:meta/meta.dart';

import '../../../../core/error/failure.dart';
import '../entities/number_trivia.dart';
import '../repositories/number_trivia_repository.dart';

class GetConcreteNumberTrivia {
  final NumberTriviaRepository repository;

  GetConcreteNumberTrivia(this.repository);

  Future<Either<Failure, NumberTrivia>> execute({
    @required int number,
  }) async {
    return await repository.getConcreteNumberTrivia(number);
  }
}
```

# まとめと感想
## Clean Architectureの中央からコードを書く
- 変更しないものから書くという考え方になる
- 外側から内側に依存する設計なので、内側から順に作成できる
    - ただし、UseCaseは外側のRepositoryからデータを受け取る。CleanArchitectureはこれを依存性逆転の原則で解決している
        - UseCaseはRepositoryの実装には依存せず、インターフェイスに依存する
- **Repositoryの実装が存在しない状態でUseCaseを実装するには、Repositoryのインターフェイスとmockを用いたTDDが自然な手法となる**
    - **つまり、CleanArchitectureをしようとするとTDDするのが１つの自然な方法となる。**
    - TDD以外だとインターフェイスを継承したFakeClassを作って実装をすすめることがある
- テストコードは3段階になる
    - 事前準備: mock等の設定
    - 実行: テスト対象メソッドを実行
    - 検証: メソッドの実行結果を検証