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
12
5.4k
テストの可読性を支える技術
テストの可読性は、とても大事です。
そんな可読性をあげてくれるパッケージや考え方などを解説したので
ぜひご覧いただければ幸いです。
ashdik
October 23, 2021
Tweet
Share
More Decks by ashdik
See All by ashdik
4年間変わらなかった YOUTRUSTのアーキテクチャ
daiki1003
3
2k
DevTools Extension 3分クッキング
daiki1003
0
1.1k
barrel_file_for_flutter.pdf
daiki1003
1
1.6k
YOUTRUSTアプリの構造、暴露してみた
daiki1003
7
3.8k
Featured
See All Featured
30 Presentation Tips
portentint
PRO
1
210
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
26
3.3k
Exploring anti-patterns in Rails
aemeredith
2
250
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
196
71k
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
2
410
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2k
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
0
400
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
180
brightonSEO & MeasureFest 2025 - Christian Goodrich - Winning strategies for Black Friday CRO & PPC
cargoodrich
3
97
Navigating the Design Leadership Dip - Product Design Week Design Leaders+ Conference 2024
apolaine
0
170
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
240
Statistics for Hackers
jakevdp
799
230k
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 հ͖͠Εͳ͔ͬͨύοέʔδͨͪ
ਖ਼ࣾһ Λݟਾ͑ͨۀҕୗͷํ ืूதʂ
͝ਗ਼ௌɺ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ