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
400
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
440
Other Decks in Programming
See All in Programming
PumaからFalconに移行したRailsアプリケーションでの実践
falcon8823
0
170
The Rails Doctrine Decade
koic
2
590
sbt 2.0: takeoff
eed3si9n
0
160
golang-bob.pdf
iwatsukayura
0
130
IBM Bob Dojo #1 仕様駆動開発入門
oniak3ibm
PRO
0
390
Unity6 URPによる海底をテーマにしたVJシステムの解説 | Unity Shader 完全に理解した 勉強会
gam0022
0
370
GemmaをJevのように使ってみる / Use Gemma like Jev
kishida
5
790
月間100万人のサービス刷新で、 技術選定をHonoに振り切った話
yutobara
8
6.6k
自サークルのIdPからログアウトできるようになるまで
taiseiue
1
140
Simple Storage Service(S3) is not simple
iwatsukayura
0
290
全ての永続トークンを、生まれる前に消し去りたい!
sylph01
2
880
Workers Cache を知る
syumai
0
360
Featured
See All Featured
Building the Perfect Custom Keyboard
takai
2
910
AI: The stuff that nobody shows you
jnunemaker
PRO
10
1.2k
Site-Speed That Sticks
csswizardry
13
1.6k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
3.2k
The Cult of Friendly URLs
andyhume
79
7k
How to train your dragon (web standard)
notwaldorf
96
6.9k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
1k
The browser strikes back
jonoalderson
0
1.8k
sira's awesome portfolio website redesign presentation
elsirapls
0
450
Building Adaptive Systems
keathley
44
3.2k
The SEO identity crisis: Don't let AI make you average
varn
0
590
[RailsConf 2023] Rails as a piece of cake
palkan
59
7.1k
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 まとめ:テストから書くのはいいぞぉ