前言
以前專職開發 Android 專案時,我喜愛 Clean Architecture + MVVM 架構當作基本配置;TDD 一直是我很想精通的一個技能,但礙於各種因素,正式工作中我一向是在最後階段才補上測試。這次學習 Flutter 課程,除了想將技能樹向外延伸,也想複習架構,並順便習慣 TDD。
課程
本次課程選用 Matt Rešetár 的 Flutter TDD Clean Architecture Course:
這個課程適合熟悉 Flutter 或是熟悉 TDD 及 Clean Architecture 的開發者,若沒有基礎就直接來上,可能資訊量會太大;以我來說,我習慣使用 Clean Architecture 且接觸過 TDD,有疑惑的部分就會是 Flutter / Dart 為主。
筆記
以下為我觀看教學影片的筆記,並不會完整記載影片重點,但會針對我不熟悉的內容來留下紀錄,可能無法滿足所有人,可以斟酌瀏覽。
Flutter TDD Clean Architecture Course [1] – Explanation & Project Structure
第一集主要在介紹 Clean Architecture,教學專案結構,雖然作者是用 VS Code 進行開發,但對 Android Studio 開發者並無障礙。

Flutter TDD Clean Architecture Course [2] – Entities & Use Cases
第二集開頭貼了這個專案會用到的 dependency libs,很多 lib 的版本號可能都太舊了,為了避免無法進行後續的課程,就先維持他貼的版本。
class NumberTrivia extends Equatable {
final String text;
final int number;
NumberTrivia({
@required this.text,
@required this.number,
}) : super([text, number]);
}
在新增第一個 entity 時,有用到 equatable 以及 meta 兩個 package,為了使用 equatable,必須將 class member 都丟給父類別,這樣才有辦法比較,不用 override equals,滿方便的。至於 meta,當然就是增加專案的可分析性、可讀性。
abstract class NumberTriviaRepository {
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int number);
Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia();
}
講到 error handling 時,作者提到 dartz 是用來處理回傳二選一 class 的 package,教學中寫成 Either<FailureModel, SuccessModel>。此外,Future 是用來處理非同步的 class。


void main() {
GetConcreteNumberTrivia usecase;
MockNumberTriviaRepository mockNumberTriviaRepository;
setUp(() {
mockNumberTriviaRepository = MockNumberTriviaRepository();
usecase = GetConcreteNumberTrivia(mockNumberTriviaRepository);
});
final tNumber = 1;
final tNumberTrivia = NumberTrivia(number: tNumber, text: "test");
test(
'should get trivia for the number from the repository',
() async {
// arrange
when(mockNumberTriviaRepository.getConcreteNumberTrivia(any))
.thenAnswer((_) async => Right(tNumberTrivia));
// act
final result = await usecase.execute(number: tNumber);
// assert
expect(result, tNumberTrivia);
verify(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
},
);
}
setUp() 在執行每一個單一測試之前都會跑過一遍,在 Android 單元測試也有一樣的 method。
作者在測試中採用 mockito 來 mock 所有物件,值得注意的是:因為要測非同步的回應,所以不能用 .thenReturn(),而是要使用 .thenAnwser() 來處理。
verify() 是為了確認 mock 物件有沒有確實呼叫函式的 mockito method。
Flutter TDD Clean Architecture Course [3] – Domain Layer Refactoring
// before
Future<Either<Failure, NumberTrivia>> execute({@required int number}) async {
return await repository.getConcreteNumberTrivia(number);
}
final result = await usecase.execute(number: tNumber);
// after
Future<Either<Failure, NumberTrivia>> call({@required int number}) async {
return await repository.getConcreteNumberTrivia(number);
}
final result = await usecase(number: tNumber);
影片一開始就做了一個重構,將 use case 的 execute method rename 為 call,這時神奇的事情發生了,他也沒去呼叫 usecase.call(),就只留下 usecase();原來 Dart 提供 callable class,實作方法就是直接新增一個 call(),就可以將 class 當作 function 來使用,這個用法等同於 Kotlin 的 operator fun invoke()。
接著是基礎建設,將 use case 抽出一個 base class,主要是想讓所有 use case 統一標準,避免不同的 use case 有不同的使用方法,理由滿好笑但也很真實:工程師會忘記。
影片後期就是依樣畫葫蘆再新增一個 use case,這集主要就是 use case 的收尾。
專案連結
想看我實作的完整專案請瀏覽我的 github:





留言列表