前言
以前專職開發 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:
留言列表