Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
テストの可読性を支える技術
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
ashdik
October 23, 2021
5.5k
12
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
テストの可読性を支える技術
テストの可読性は、とても大事です。
そんな可読性をあげてくれるパッケージや考え方などを解説したので
ぜひご覧いただければ幸いです。
ashdik
October 23, 2021
More Decks by ashdik
See All by ashdik
4年間変わらなかった YOUTRUSTのアーキテクチャ
daiki1003
3
2.1k
DevTools Extension 3分クッキング
daiki1003
0
1.2k
barrel_file_for_flutter.pdf
daiki1003
1
1.7k
YOUTRUSTアプリの構造、暴露してみた
daiki1003
7
3.9k
Featured
See All Featured
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
62
45k
Into the Great Unknown - MozCon
thekraken
41
2.6k
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.3k
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
190
How to train your dragon (web standard)
notwaldorf
97
6.7k
Building a Scalable Design System with Sketch
lauravandoore
463
34k
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
0
350
Are puppies a ranking factor?
jonoalderson
1
3.7k
Fireside Chat
paigeccino
42
4k
The untapped power of vector embeddings
frankvandijk
2
1.8k
Music & Morning Musume
bryan
47
7.3k
Making the Leap to Tech Lead
cromwellryan
135
10k
Transcript
ேେथ ςετͷՄಡੑ Λࢧ͑Δٕज़
ேେथ 5XJUUFSɿ!EBJLJ 'MVUUFSྺɿ ϒϩάɿIUUQTCMPHEBMUNF
࣍ w ςετՄಡੑ͕େࣄ w ςετͷՄಡੑ ฐࣾͷϏϑΥʔΞϑλʔ w ςετ݁ՌͷՄಡੑ
ࠓͷඪ ͎͘ʙɺςετͷՄಡੑ͋͛ͯ͐ʙ
ຊͷ༰ IUUQTHJUIVCDPNEBJLJ fl VUUFS@UFTU@XJUI@SFBEBCJMJUZ
ຊͷ༰
ຊͷྲྀΕ
Կҙࣝͤͣॻ͍ͨ ςετ͕͋Δ
None
͜ΕΛ ͭͷύοέʔδͳͲΛ ͬͯՄಡੑΛ্͛ͯΈΑ͏ʂ
ͱɺͦͷલʹʂ
ͦͦ ςετʹՄಡੑඞཁ͔ʁ
:&4ʂ
ςετʹՄಡੑඞཁ͔ʁ w ςετίʔυͷઆ໌ॻ w ͪΖΜਖ਼͘͠ಈ͔͘Ͳ͏͔Λ୲อ͢Δͱ͍͏ଆ໘͋Δ ͦͷҰํͰɺ w ࢲ͜͏͍͏෩ʹಈ͘ίʔυΛॻ͍ͨΑ w ͜͏͍͏ೖྗΛఆ͍ͯ͠ΔΑ
w ͜͏͍͏࣌ΤϥʔΛग़͢Α ͱ͍͏આ໌ॻɺҙࢥද໌ʹͳ͍ͬͯΔ
ςετͷՄಡੑ͕͍ ʹ Կ͕ݴ͍͍ͨͷ͔͔Βͳ͍ ςετʹՄಡੑඞཁ͔ʁ
͔ͩΒɺςετ ಡΈ͘͢ॻ͜͏ͳʁ ݁
ςετͷՄಡੑ w HSPVQ w HJWFO@XIFO@UIFO w fl VUUFS@UFTU@VJ
HSPVQ
ςετͷՄಡੑ w ͝ଘͷํଟ͍ͣ w fl VUUFS@UFTUʹಉࠝ͞Ε͍ͯΔϝιου w ͦͷ໊ͷ௨ΓɺෳͷςετΛάϧʔϓԽग़དྷΔ HSPVQ
None
JODSFNFOUCVUUPO EFDSFNFOUCVUUPO
ςετͷՄಡੑ
ςετͷՄಡੑ group('[Increment Button]', () { group('label shows "0"', () {
testWidgets('label shows "1"', (WidgetTester tester) async { }); }); group('label shows "1"', () { testWidgets('label shows "2"', (WidgetTester tester) async { }); }); });
ςετͷՄಡੑ group('[Decrement Button]', () { group('label shows "1"', () {
testWidgets('label shows "0"', (WidgetTester tester) async { }); }); group('label shows "2"', () { testWidgets('label shows "1"', (WidgetTester tester) async { }); }); });
HSPVQ ݸॏͳͬͱΔΜ͚Ͳʁ
ςετͷՄಡੑ group('[Increment Button]', () { group('label shows "0"', () {
testWidgets('label shows "1"', (WidgetTester tester) async { }); }); group('label shows "1"', () { testWidgets('label shows "2"', (WidgetTester tester) async { }); }); });
ςετͷՄಡੑ group('[Increment Button]', () { ← describe group('label shows "0"',
() { ← context testWidgets('label shows "1"' ← it, (WidgetTester tester) async { }); }); group('label shows "1"', () { testWidgets('label shows "2"', (WidgetTester tester) async { }); }); });
ςετͷՄಡੑ w 34QFDͰ࠾༻͞Ε͍ͯΔه๏ w EFTDSJCF ɹςετର w DPOUFYU ɹςετΛߦ͏લఏ݅ w
JU ɹͲ͏ͳ͍ͬͯΔ͖͔ JUTIPVMECFͷҙຯͷJU EFTDSJCF DPOUFYU JUFYBNQMF
ςετͷՄಡੑ group('[Increment Button]', () { ← describe group('label shows "0"',
() { ← context testWidgets('label shows "1"' ← it, (WidgetTester tester) async { }); }); group('label shows "1"', () { testWidgets('label shows "2"', (WidgetTester tester) async { }); }); });
ςετͷՄಡੑ group('[Increment Button]', () { ← describe group('label shows "0"',
() { ← context testWidgets('label shows "1"' ← it, (WidgetTester tester) async { }); }); group('label shows "1"', () { testWidgets('label shows "2"', (WidgetTester tester) async { }); }); });
ςετͷՄಡੑ group('[Increment Button]', () { ← describe group('label shows "0"',
() { ← context testWidgets('label shows "1"' ← it, (WidgetTester tester) async { }); }); group('label shows "1"', () { testWidgets('label shows "2"', (WidgetTester tester) async { }); }); });
ςετͷՄಡੑ group('[Increment Button]', () { group('label shows "0"', () {
testWidgets('label shows "1"', (WidgetTester tester) async { }); }); group('label shows "1"', () { testWidgets('label shows "2"', (WidgetTester tester) async { }); }); });
HJWFO@XIFO@UIFO
ςετͷՄಡੑ ςετͬͯ݁ہͷͱ͜Ζ͜ΜͳߏͰΓཱ͍ͬͯΔ HJWFO@XIFO@UIFO
ςετͷՄಡੑ ͋Δ݅Ͱɺ͋Δಈ࡞Λͨ͠Β͜͏ͳͬͯ΄͍͠ HJWFO@XIFO@UIFO
ςετͷՄಡੑ ͋Δ݅Ͱɺ͋Δಈ࡞Λͨ͠Β͜͏ͳͬͯ΄͍͠ HJWFO@XIFO@UIFO HJWFO
ςετͷՄಡੑ ͋Δ݅Ͱɺ͋Δಈ࡞Λͨ͠Β͜͏ͳͬͯ΄͍͠ HJWFO@XIFO@UIFO XIFO HJWFO
ςετͷՄಡੑ ͋Δ݅Ͱɺ͋Δಈ࡞Λͨ͠Β͜͏ͳͬͯ΄͍͠ HJWFO@XIFO@UIFO UIFO XIFO HJWFO
ྫ͑
ςετͷՄಡੑ ͕දࣔ͞Ε͍ͯΔ࣌ʹ ϓϥεϘλϯΛԡͨ͠Β ͕දࣔ͞Ε͍ͯͯཉ͍͠ HJWFO@XIFO@UIFO HJWFO
ςετͷՄಡੑ ͕දࣔ͞Ε͍ͯΔ࣌ʹ ϓϥεϘλϯΛԡͨ͠Β ͕දࣔ͞Ε͍ͯͯཉ͍͠ HJWFO@XIFO@UIFO HJWFO XIFO
ςετͷՄಡੑ ͕දࣔ͞Ε͍ͯΔ࣌ʹ ϓϥεϘλϯΛԡͨ͠Β ͕දࣔ͞Ε͍ͯͯཉ͍͠ HJWFO@XIFO@UIFO HJWFO XIFO UIFO
ςετͷՄಡੑ HJWFO@XIFO@UIFO testWidgets('label shows "1"', harness((given, when, then) async {
{ await given.pumpMyApp(); given.canFindZero(); given.cannotFindOne(); } { await when.userTapsIncrementButton(); } { then.cannotFindZero(); then.canFindOne(); } }), );
ςετͷՄಡੑ HJWFO@XIFO@UIFO testWidgets('label shows "1"', harness((given, when, then) async {
{ await given.pumpMyApp(); given.canFindZero(); given.cannotFindOne(); } { await when.userTapsIncrementButton(); } { then.cannotFindZero(); then.canFindOne(); } }), ); ͕දࣔ͞Ε͍ͯΔ࣌ʹ
ςετͷՄಡੑ HJWFO@XIFO@UIFO testWidgets('label shows "1"', harness((given, when, then) async {
{ await given.pumpMyApp(); given.canFindZero(); given.cannotFindOne(); } { await when.userTapsIncrementButton(); } { then.cannotFindZero(); then.canFindOne(); } }), ); ϓϥεϘλϯ͕ԡ͞ΕͨΒ ͕දࣔ͞Ε͍ͯΔ࣌ʹ
ςετͷՄಡੑ HJWFO@XIFO@UIFO testWidgets('label shows "1"', harness((given, when, then) async {
{ await given.pumpMyApp(); given.canFindZero(); given.cannotFindOne(); } { await when.userTapsIncrementButton(); } { then.cannotFindZero(); then.canFindOne(); } }), ); ϓϥεϘλϯ͕ԡ͞ΕͨΒ ͕දࣔ͞Ε͍ͯΔ࣌ʹ ͕දࣔ͞Ε͍ͯͯཉ͍͠
ςετͷՄಡੑ ղઆ HJWFO@XIFO@UIFO
ςετͷՄಡੑ HJWFO@XIFO@UIFO testWidgets('label shows "1"', harness((given, when, then) async {
{ await given.pumpMyApp(); given.canFindZero(); given.cannotFindOne(); } { await when.userTapsIncrementButton(); } { then.cannotFindZero(); then.canFindOne(); } }), );
ςετͷՄಡੑ HJWFO@XIFO@UIFO
ςετͷՄಡੑ HJWFO@XIFO@UIFO extension MyAppGiven on WidgetTestGiven<_MyAppTestHarness> { Future<void> pumpMyApp() async
{ await tester.pumpWidget(const MyApp()); } Future<void> increment() async { await tester.tap(find.byKey(incrementKey)); } Future<void> pump() async { await tester.pump(); } void canFindZero() { expect(find.text('0'), findsOneWidget); } void cannotFindZero() { expect(find.text('0'), findsNothing); }}
ςετͷՄಡੑ HJWFO@XIFO@UIFO extension MyAppWhen on WidgetTestWhen<_MyAppTestHarness> { Future<void> userTapsIncrementButton() async
{ await tester.tap(find.byKey(incrementKey)); await tester.pump(); } Future<void> userTapsDecrementButton() async { await tester.tap(find.byKey(decrementKey)); await tester.pump(); } }
ςετͷՄಡੑ HJWFO@XIFO@UIFO extension MyAppThen on WidgetTestThen<_MyAppTestHarness> { void canFindZero() {
expect(find.text('0'), findsOneWidget); } void cannotFindZero() { expect(find.text('0'), findsNothing); } void canFindOne() { expect(find.text('1'), findsOneWidget); } void cannotFindOne() { expect(find.text('1'), findsNothing); } void canFindTwo() { expect(find.text('2'), findsOneWidget); } void cannotFindTwo() { expect(find.text('2'), findsNothing); } }
ςετͷՄಡੑ HJWFO@XIFO@UIFO testWidgets('label shows "1"', harness((given, when, then) async {
{ await given.pumpMyApp(); given.canFindZero(); given.cannotFindOne(); } { await when.userTapsIncrementButton(); } { then.cannotFindZero(); then.canFindOne(); } }), );
ςετͷՄಡੑ HJWFO@XIFO@UIFO testWidgets('label shows "2"', harness((given, when, then) async {
{ await given.pumpMyApp(); await given.increment(); await given.pump(); given.canFindOne(); given.cannotFindTwo(); } { await when.userTapsIncrementButton(); } { then.cannotFindOne(); then.canFindTwo(); } }));
testWidgets('label shows "1"', harness((given, when, then) async { { await
given.pumpMyApp(); await given.increment(); await given.increment(); await given.pump(); given.canFindTwo(); given.cannotFindOne(); } { await when.userTapsDecrementButton(); } { then.cannotFindTwo(); then.canFindOne(); } })); ςετͷՄಡੑ HJWFO@XIFO@UIFO
ςετͷՄಡੑ HJWFO@XIFO@UIFO ͪΐͬͱେม͡ΌͶʁ
ςετͷՄಡੑ HJWFO@XIFO@UIFOͷ؆қ൛ testWidgets('label shows "1"', (WidgetTester tester) async { //
given { await tester.pumpWidget(const MyApp()); expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); } // when { await tester.tap(find.byKey(incrementKey)); await tester.pump(); } // then { expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); } });
fl VUUFS@UFTU@VJ
ςετͷՄಡੑ GMVUUFS@UFTU@VJ void main() { setUpUI((tester) async { await tester.pumpWidget(const
MyApp()); }); testUI('label shows "1"', harness((given, when, then) async { { given.canFindZero(); given.cannotFindOne(); } { await when.userTapsIncrementButton(); } { then.cannotFindZero(); then.canFindOne(); } })); }
ςετͷՄಡੑ GMVUUFS@UFTU@VJ w testUI, setupUI, tearDownUIͳͲɺ6*ςετ༻ͷϝιουΛఏڙ
ςετͷՄಡੑ GMVUUFS@UFTU@VJ testWidgets('label shows "1"', harness((given, when, then) async {
{ await given.pumpMyApp(); given.canFindZero(); given.cannotFindOne(); } { await when.userTapsIncrementButton(); } { then.cannotFindZero(); then.canFindOne(); } }), );
ςετͷՄಡੑ HJWFO@XIFO@UIFO testWidgets('label shows "1"', harness((given, when, then) async {
{ await given.pumpMyApp(); given.canFindZero(); given.cannotFindOne(); } { await when.userTapsIncrementButton(); } { then.cannotFindZero(); then.canFindOne(); } }), );
ςετͷՄಡੑ GMVUUFS@UFTU@VJ void main() { setUpUI((tester) async { await tester.pumpWidget(const
MyApp()); }); testUI('label shows "1"', harness((given, when, then) async { { given.canFindZero(); given.cannotFindOne(); } { await when.userTapsIncrementButton(); } { then.cannotFindZero(); then.canFindOne(); } })); }
ςετͷՄಡੑ GMVUUFS@UFTU@VJ void main() { setUpUI((tester) async { await tester.pumpWidget(const
MyApp()); }); testUI('label shows "1"', harness((given, when, then) async { { given.canFindZero(); given.cannotFindOne(); } { await when.userTapsIncrementButton(); } { then.cannotFindZero(); then.canFindOne(); } })); }
ςετͷՄಡੑ GMVUUFS@UFTU@VJ void main() { setUpUI((tester) async { await tester.pumpWidget(const
MyApp()); }); testUI('label shows "1"', harness((given, when, then) async { { given.canFindZero(); given.cannotFindOne(); } { await when.userTapsIncrementButton(); } { then.cannotFindZero(); then.canFindOne(); } })); }
group('label shows "2"', () { setUpUI((tester) async { await tester.tap(find.byKey(incrementKey));
await tester.tap(find.byKey(incrementKey)); await tester.pump(); }); testUI('label shows "1"', harness((given, when, then) async { { given.canFindTwo(); given.cannotFindOne(); } { await when.userTapsDecrementButton(); } { then.cannotFindTwo(); then.canFindOne(); } })); }); ςετͷՄಡੑ GMVUUFS@UFTU@VJ
ฐࣾͰͷϏϑΥʔΞϑλʔ
ฐࣾͰͷϏϑΥʔΞϑλʔ "3FRVFTU #3FRVFTU $3FRVFTU "QJ$MJFOU "3FTQPOTF #3FTQPOTF $3FTQPOTF $PNNBOEύλʔϯ
"QJ 3FRV FTU
"3FRVFTU #3FRVFTU $3FRVFTU "QJ$MJFOU "3FTQPOTF #3FTQPOTF $3FTQPOTF $PNNBOEύλʔϯ "QJ
3FRV FTU ฐࣾͰͷϏϑΥʔΞϑλʔ
"3FRVFTU #3FRVFTU $3FRVFTU "QJ$MJFOU "3FTQPOTF #3FTQPOTF $3FTQPOTF $PNNBOEύλʔϯ "QJ
3FRV FTU ฐࣾͰͷϏϑΥʔΞϑλʔ
"3FRVFTU #3FRVFTU $3FRVFTU "QJ$MJFOU "3FTQPOTF #3FTQPOTF $3FTQPOTF $PNNBOEύλʔϯ "QJ
3FRV FTU ฐࣾͰͷϏϑΥʔΞϑλʔ
ฐࣾͰͷϏϑΥʔΞϑλʔ
ฐࣾͰͷϏϑΥʔΞϑλʔ
ฐࣾͰͷϏϑΥʔΞϑλʔ
ฐࣾͰͷϏϑΥʔΞϑλʔ
ฐࣾͰͷϏϑΥʔΞϑλʔ ςετΛͭՃ͢ΔͨΊʹ༷ʑͳϑΝΠϧΛՃมߋ͢Δඞཁ͕͋Δ w UFTUSFTPVSDFTԼ w KTPOͷՃ w 'BLF"QJ$MJFOU w ϞοΫύεͷઃఆ
w ΕΔͱImplementationError w xxxTest σϝϦοτ
ฐࣾͰͷϏϑΥʔΞϑλʔ ͪΐͬͱ͚ͩҧ͏ϨεϙϯεΛ࡞Δͷʹ৽ͨͳKTPO͕ඞཁ ɾ༑ୡͷ༗ແ ɾϑΥϩʔঢ়ଶ ɾϒϩοΫঢ়ଶ σϝϦοτ
ฐࣾͰͷϏϑΥʔΞϑλʔ σϝϦοτ มߋʹରͯ͠ίϯύΠϧηʔϑͰͳ͍ ɾ6TFS͔ΒϓϩύςΟΛҰͭݮΒͨ͠ ɹɹKTPOม͑ͳ͖Ό͍͚ͳ͍͜ͱͳΜͯ୭ؾ͔ͳ͍ΑͶস
Ξϑλʔ
ฐࣾͰͷϏϑΥʔΞϑλʔ Ξϑλʔ typedef FakeApiResponder = dynamic Function(ApiRequest<dynamic>);
ฐࣾͰͷϏϑΥʔΞϑλʔ Ξϑλʔ FakeApiClientImpl appending({ required FakeApiResponder responder, }) { return
FakeApiClientImpl._( responders: [..._responders, responder], ); }
)PXUPVTFʁ
ฐࣾͰͷϏϑΥʔΞϑλʔ Ξϑλʔ void main() { final baseApiClient = FakeApiClientImpl.create(); setUpAll(TestWidgetsFlutterBinding.ensureInitialized);
// Test cases... }
ฐࣾͰͷϏϑΥʔΞϑλʔ Ξϑλʔ final apiClient = baseApiClient.appending( responder: (request) { if
(request is UsersRequest) { return _Fixtures.createUser( id: 'some_user_id', ); } }, );
ฐࣾͰͷϏϑΥʔΞϑλʔ Ξϑλʔ class _Fixtures { static User createUser() { return
User(id: 'hoge', name: 'ashdik'); } }
ฐࣾͰͷϏϑΥʔΞϑλʔ ςετΛͭՃ͢ΔͨΊʹ༷ʑͳϑΝΠϧΛՃมߋ͢Δඞཁ͕͋Δ ɹˠͦΕͧΕͷςετͷΈʂ ͪΐͬͱ͚ͩҧ͏ϨεϙϯεΛ࡞Δͷʹ৽ͨͳKTPO͕ඞཁ ɹˠ@'JYUVSFͷҾʹม͍͑ͨΛऔΔ͚ͩ มߋʹରͯ͠ίϯύΠϧηʔϑͰͳ͍ ɹˠ6TFSΫϥεΛΠϯελϯεԽ͍ͯ͠Δ σϝϦοτ
ςετ݁ՌͷՄಡੑ
EBSU@EPU@SFQPSUFS
ςετ݁ՌͷՄಡੑ w σϑΥϧτͰ ओʹࣦഊ࣌ ಡΈͮΒ͍ʜ GMVUUFSUFTU
ςετ݁ՌͷՄಡੑ w ಡΈ͍͢🎉
ςετ݁ՌͷՄಡੑ w EPU@SFQPSUFSΠϯεύΠΞ w ςετ݁ՌΛݟͯ͘͘͢͠ΕΔ w ʮʯ ɹޭ w ʮ9ʯ
ɹࣦഊ w ʮʯ ɹεΩοϓ EBSU@EPU@SFQPSUFS
ςετ݁ՌͷՄಡੑ ͩΜͩΜಡΈͯ͘͢͠ΈΑ͏ GMVUUFSUFTUNBDIJOF
w ҎԼ2εςοϓ ςετ݁ՌͷՄಡੑ w pubspec.yamlʹdart_dot_reporterΛՃ EBSU@EPU@SFQPSUFSɹʙԼ४උʙ GMVUUFSQVCHMPCBMBDUJWBUFEBSU@EPU@SFQPSUFS
ςετ݁ՌͷՄಡੑ ͩΜͩΜಡΈͯ͘͢͠ΈΑ͏ GMVUUFSUFTUNBDIJOFNBDIJOFMPH GMVUUFSQVCHMPCBMSVOEBSU@EPU@SFQPSUFSNBDIJOFMPH
ςετ݁ՌͷՄಡੑ ͩΜͩΜಡΈͯ͘͢͠ΈΑ͏
ͦͷଞ w SJWFSQPE w fl VUUFS@HIFSLJO w NPDLJOHKBZ w NPDLJUP
w CEE@XJEHFU@UFTU w PHVSFUT հ͖͠Εͳ͔ͬͨύοέʔδͨͪ
ਖ਼ࣾһ Λݟਾ͑ͨۀҕୗͷํ ืूதʂ
͝ਗ਼ௌɺ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ