Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Flutterでもテスト駆動したい
Search
Takashi Makino
May 19, 2023
Programming
390
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Flutterでもテスト駆動したい
Takashi Makino
May 19, 2023
More Decks by Takashi Makino
See All by Takashi Makino
あなたの知らないスクラムの世界
makky0620
0
120
A_アルゴリズム高速化を目指して
makky0620
0
1.6k
Discord botにScrumの Discord botにScrumの手伝いをしてもらう手伝いをしてもらう
makky0620
0
430
Other Decks in Programming
See All in Programming
Omarchy Tokyo やると聞いて UMPC 買ってセットアップしてきた
mtsmfm
0
140
AI駆動開発にグラフDBを重ねてみた
satoshi256kbyte
2
780
『寄り添うラジオ』をAIで作る 体験価値から逆算した、会話しないUXと品質設計
theoriatec2024
3
150
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
260
コンパウンドプロダクト開発のためのローカルプロセスマネージャー再発明 #layerxgo
izumin5210
0
670
デプロイ直後のレイテンシスパイクを調べたら、 Railsの仕様にたどり着いた
nhsykym
0
110
Security issues being discussed on Web Platforms
petamoriken
0
760
Vibes Containers 〜AIで変わるコンテナ設計と運用〜
tkikuc
3
530
How I Stole PSI from Android Studio - DroidKaigi2026
worker8
0
110
標準パッケージに uuid が追加された 背景から見る Go らしい意思決定 / go_127_uuid_decision
convto
5
6.2k
The Good Stuff, Not the Slop: Engineering High-Quality Android Apps with Modern AI Tooling
danybony
1
240
モバイル交通系ICへのチャージ実例から考える、クロスプラットフォーム開発におけるiOS実機テスト設計とCI運用
yusuga
1
350
Featured
See All Featured
WCS-LA-2024
lcolladotor
0
830
The agentic SEO stack - context over prompts
schlessera
0
920
VelocityConf: Rendering Performance Case Studies
addyosmani
331
25k
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
660
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.6k
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
6.2k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
410
The Mindset for Success: Future Career Progression
greggifford
PRO
0
490
Technical Leadership for Architectural Decision Making
baasie
3
560
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
380
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.8k
Music & Morning Musume
bryan
47
7.4k
Transcript
Flutterでもテスト駆動したい 牧野孝史@合同LT会 in 横浜 たくさんFlutterを触っている⼈には当たり前かも… もっとこうした⽅が良いがあれば教えてください
Takashi Makino makky0620 未来シェア Overview About me 未来シェア 最適化っぽいコードを書いている ただ、フロントもAPIも⾊々やる!
公⽴はこだて未来⼤学 ⺟校‧北海道函館市にある情報系の⼤学 今回のLT会を企画するきっかけに ⼭の上にあって⾃転⾞で⾏くのは⼤変 キャンプ 最近キャンプしてきて趣味にしたいと思っている 焚き⽕と飯が最⾼ Engineering Recent memory
Takashi Makino makky0620 未来シェア Overview Popular language or frameworks Python
さくっと書きたい時に使う 仕事ではちょっとしたスクリプト作成 趣味では機械学習で使⽤ Java(Spring Boot) はじめましては⼤学1年⽣ Spring Bootをはじめたのは仕事をはじめてから オブジェクト指向とStreamAPIが好き TypeScript(React) ⼤学時代のバイトで使いはじめた(当時はJS) フロントアプリを作るといえばこいつ 最近あまり書いてない Dart(Flutter) 2023年1⽉から始めた(2018年に⼀度挫折) 仕事でネイティブアプリが作りたくなった 徐々にわかってきた気がしたので今⽇発表 Engineering
テスト駆動開発 Test Driven Development: TDD
テスト駆動開発とは ユースケース検討 テストを書く きれいにする 成功するコードを書く RED GREEN REFACTOR
具体的にどうやるの? 受け取った⽂字列が数値かどうか判定する関数を作りたい // テストコード // プロダクションコード
具体的にどうやるの? 受け取った⽂字列が数値かどうか判定する関数を作りたい void main() { test(ʻ数値である時trueを返す’, () { expect(isNumeric(ʻ123’), true);
}); } bool isNumeric(String text) { throw UnsupportedError(ʻerror’); } // テストコード // プロダクションコード
具体的にどうやるの? 受け取った⽂字列が数値かどうか判定する関数を作りたい void main() { test(ʻ数値である時trueを返す’, () { expect(isNumeric(ʻ123’), true);
}); } bool isNumeric(String text) { return true; } // テストコード // プロダクションコード
具体的にどうやるの? 受け取った⽂字列が数値かどうか判定する関数を作りたい void main() { test(ʻ数値である時trueを返す’, () { expect(isNumeric(ʻ123’), true);
}); // テストを追加 test(ʻ数値でない時falseを返す’, () { expect(isNumeric(ʻhoge’), false); }); } bool isNumeric(String text) { return true; } // テストコード // プロダクションコード
具体的にどうやるの? 受け取った⽂字列が数値かどうか判定する関数を作りたい void main() { test(ʻ数値である時trueを返す’, () { expect(isNumeric(ʻ123’), true);
}); test(ʻ数値でない時falseを返す’, () { expect(isNumeric(ʻhoge’), false); }); } bool isNumeric(String text) { bool isNumeric = false; try { int number = int.parse(text); isNumeric = true; } catch (e) { isNumeric = false; } return isNumeric; } // テストコード // プロダクションコード
具体的にどうやるの? 受け取った⽂字列が数値かどうか判定する関数を作りたい void main() { test(ʻ数値である時trueを返す’, () { expect(isNumeric(ʻ123’), true);
}); test(ʻ数値でない時falseを返す’, () { expect(isNumeric(ʻhoge’), false); }); } bool isNumeric(String text) { return int.tryParse(text) != null; } // テストコード // プロダクションコード
依存性注⼊ Dependency Injection: DI
DIとは? 必要なものを外部から⽤意するような設計パターン class UserService { final UserRepository _repository; UserService(this._userRepository); Future<User>
getUser(String id) async { return await _repository.fetchBy(id); } } void main() async { final repository = UserRepository(); final service = UserService(repository); final user = await service.getUser(ʻ123’); } class UserService { final UserRepository _repository; UserService() { _repository = UserRepository(); }; Future<User> getUser(String id) async { return await _repository.fetchBy(id); } } void main() async { final service = UserService(); final user = await service.getUser(ʻ123’); } // DI使⽤ // DI未使⽤
DIを使うとテストが書きやすくなる? 依存関係をモックしてテストができる class UserService { final UserRepository _repository; UserService(this._userRepository); Future<User>
getUser(String id) async { return await _repository.fetchBy(id); } } void main() async { final repository = UserRepository(); final service = UserService(repository); final user = await service.getUser(ʻ123’); } class UserRepositoryMock { Future<User> fetchBy(String id) async { return User(id: id); } } void main() { final mockRepository = UserRepositoryMock(); final service = UserService(mockRepository); test(ʻ指定されたidのユーザを返すこと’, () async { final actual = service.getUser(ʻ123’); expect(actual, User(id: ʻ123’)); }); } 依存関係をテストから分離 // テストコード
FlutterでDIやモックをするために Riverpod mockito Flutterの状態管理ライブラリ でありDIフレームワーク テスト中のクラスの依存関係を モックできるテストフレーム ワーク
RiverpodでDI final userViewModelProvider = StateNotifierProvider.autoDispose((ref) => UserViewModel(...)) class UserViewModel StateNotifier<...>
{ ... Future<void> load() async { final users = await repository.fetchAll(); state = UserState(users: users); } } class UserPage extends HooksConsumerWidget { const UserPage({Key? key}): super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { final viewModel = ref.read(userViewModelProvider.notifier) return Scaffolding(...); } } Providerを⽤いてDIコンテナに登録し、WidgetRef.readを⽤いて依存性注⼊ 依存性注⼊ DIコンテナ登録
mockitoでモックしてテスト class UserRepositoryMock { Future<User> getUser(String id) async { return
User(id: id); } } void main() { final mockRepository = UserRepositoryMock(); final service = UserService(mockRepository); test(ʻ指定されたidのユーザを返すこと’, () async { final actual = await service.getUser(ʻ123’); expect(actual, User(id: ʻ123’)); }); } @GenerateMocks([UserRepository]) void main() { final mockRepository = MockUserRepository(); final service = UserService(mockRepository); test(ʻ指定されたidのユーザを返すこと’, () async { when(mockRepository.getUser(ʻ123’)) .thenAnswer((_) => Future.value(User(id: ʻ123’))); final actual = await service.getUser(ʻ123’); verify(mockRepository.getUser(ʻ123’)).called(1); expect(actual, User(id: ʻ123’)); }); } // mokito未使⽤ // mokito使⽤ whenで戻り値の設定、verifyでモックインスタンスの呼び出し検証ができる
実践 わかるけどできないんだよなぁ
Todoアプリを例にテスト駆動開発してみる Todoの⼀覧表⽰ 新規Todoの作成 Todoの⼀覧表⽰ ※本当は適当に作った画⾯イメージで良い
どんな機能を実装したい? 1. 右下のボタンを押した時、新規作成画⾯が表⽰されること 2. Todoの項⽬があった時、タイトルが表⽰されていること 3. TBD... Todoアプリを例にテスト駆動開発してみる
右下のボタンどうしようかなぁ → FloatingActionButtonで実装しよう 遷移後の画⾯の名前どうしようかな → TaskEditPageにしよう テスト駆動開発を実践 void main() {
testWidgets(ʻテストケース名', (tester) async { // 対象画⾯の描画 await tester.pumpWidget(ProviderScope( child: const MaterialApp(home: HomePage()))) // ボタンを押す await tester.tap(find.byType(FloatingActionButton)); await tester.pumpAndSettle(); // 新規作成画⾯への遷移を検証 expect(find.byType(TaskEditPage), findsOneWidget); }); } 「右下のボタンを押した時、新規作成画⾯が表⽰されること」をテストする
Todoの情報はどこから持ってくる? → Repository層から持ってくる どこに表⽰させようかな → TaskItemウィジェットにを作ってそ の中に表⽰しよう テスト駆動開発を実践 void main()
{ testWidgets(ʻテストケース名', (tester) async { // Todo項⽬のモック var task = Task( when(taskRepository.fetchAll()) .thenAnswer((_) => Future.value([task])); // 対象画⾯の描画 await tester.pumpWidget(ProviderScope( child: const MaterialApp(home: HomePage()))) await tester.pumpAndSettle(); // タイトルが表⽰されていることの検証 var titleFinder = find.descendant( of: find.byType(TaskItem), matching: find.text(ʻタイトル’) ); expect(find.byType(TaskEditPage), findsOneWidget); }); } 「Todoの項⽬があった時、タイトルが表⽰されていること」をテストする 最⼩限の設計+品質の作り込み
実際に書いてみちゃうかぁ 時間があったらですが...
今⽇のキーワード • テスト駆動開発:テストから書くことで品質の⾼いコードを作る開発技法 • DI(依存性注⼊):テストしやすくするための設計パターン Flutterでテスト駆動開発するために • riverpod:DI機能を提供してくれるパッケージ • mockito:モックオブジェクトが作れるようになるパッケージ
所感(フロントのテストを書き慣れてない) • 親‧⼦ウィジェットでのテストケースの棲み分けがわからないなぁ 今⽇のコード:https://github.com/makky0620/flutter-demo まとめ:テストから書くのはいいぞぉ