Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TypeScriptとJestではじめる AWS製サーバーレス REST API のユニットテスト・E2Eテスト #serverlessfukuoka #serverlessdays / Serverless testing using TypeScript and Jest

664b6e8ebe272fcfa5dbd6070eaf3cd4?s=47 Yusuke Wada
December 14, 2019

TypeScriptとJestではじめる AWS製サーバーレス REST API のユニットテスト・E2Eテスト #serverlessfukuoka #serverlessdays / Serverless testing using TypeScript and Jest

この発表では、サーバーレスアプリケーションのユニットテストとE2Eテストを書くときの考え方とその手段について述べます。テストについて議論するときは、アプリケーション設計についても述べなければなりません。

発表の中でまず、サーバーレスアプリケーションは、レイヤ・機能別にコードを分類できることを示します。その後、分類しおたコードに対し、Jestでモック化し、テスト対象を抽出してテストするユニットテストを行います。このときモック対象の戦略についても話します。

最後にE2Eテストの有効性と実行方法を述べます。

664b6e8ebe272fcfa5dbd6070eaf3cd4?s=128

Yusuke Wada

December 14, 2019
Tweet

Transcript

  1. TypeScriptͱJestͰ͸͡ΊΔ AWS੡αʔόʔϨε REST API ͷϢχοτςετ/E2Eςετ #serverlessfukuoka

  2. ςετίʔυΛॻ͍ͯ΄͍͠

  3. αʔόʔϨε։ൃք۾ͷ՝୊ ΞʔΩςΫνϟύλʔϯ ϚωʔδυαʔϏε ֤αʔϏεͷ࢖͍ํ ΞϓϦέʔγϣϯύλʔϯ σϓϩΠύλʔϯ ςετ ॆ࣮͖ͯͨ͠ ՝୊͕࢒Δʢࣄྫෆ଍ʣ

  4. αʔόʔϨε։ൃք۾ͷ՝୊ ΞʔΩςΫνϟύλʔϯ ϚωʔδυαʔϏε ֤αʔϏεͷ࢖͍ํ ΞϓϦέʔγϣϯύλʔϯ σϓϩΠύλʔϯ ςετ ॆ࣮͖ͯͨ͠ ՝୊͕࢒Δʢࣄྫෆ଍ʣ lॳظ։ൃzʹඞཁ

    lܧଓ։ൃzʹඞཁ
  5. େࠓ೔ࢀߟʹ͍͍ͤͯͨͩͨ͞৘ใ ࿨ా୎ਓ, 2017/6/2 AWS Dev Day Tokyo 2017 IUUQTTQFBLFSEFDLDPNUXBEBUFTUBCMFMBNCEBXPSLJOHF⒎FDUJWFMZXJUIMFHBDZMBNCEB Testable

    Lambda Working Effectivery with Legacy Lambda Clean Architecture ୡਓʹֶͿιϑτ΢ΣΞͷ ߏ଄ͱઃܭ Robert C. Martin ςετۦಈ։ൃ Kent Beck, ࿨ా୎ਓ
  6. ࣗݾ঺հ ࿨ా༞հ 
 $9ࣄۀຊ෦
 αʔόʔϨεΤϯδχΞ XBEEZ@V ٕज़ॻయ7

  7. ࠓ೔ͷ͓୊ʹ࢖Θ͍͍ͤͯͨͩͨΞϓϦέʔγϣϯ 4#(JGU༷ΪϑταʔϏεόοΫΤϯυͷҰ෦

  8. αʔόʔαΠυͷςετʹ΋࢖͑Δ+FTU ΦʔϧΠϯϫϯ ςετϥϯφʔɺΞαʔγϣϯɺςετμϒϧɺΧόϨοδ Ϩϙʔτ UTKFTUͰ5ZQF4DSJQUͷςετίʔυΛ௚઀࣮ߦՄೳ "84$%,Ͱ࠾༻͞Ε͍ͯΔ

  9. ൃදͷ໨ඪ ςετίʔυΛॻ͘ϋʔυϧΛԼ͛Δ͜ͱ ͦͷͨΊʹɿ αʔόʔϨεͰlίεύͷྑ͍zςετઓज़Λ੔ཧ͢Δ
 Ϣχοτςετɾ&&ςετ͕ର৅ ͍ͭͰʹɿ αʔόʔϨεΞϓϦέʔγϣϯ։ൃʹ໾ཱͭ
 +FTUͷ5JQTΛ঺հ͢Δ

  10. ࠓ೔ग़ͯ͘ΔαʔόʔϨεΞϓϦͷલఏ w ओʹ"84αʔϏεΛ࢖ͬͯ w αʔόʔϨεͰ w ϏδωεϩδοΫΛ༗͢Δ w 8FC"1*Λ࡞Δ ͱ͍͏લఏͰ͢

  11. ΞδΣϯμ ޮ཰ͷྑ͍ςετઓज़ ! ϢχοτςετͱΞϓϦέʔγϣϯઃܭ E2EςετͱΞʔΩςΫνϟύλʔϯ ·ͱΊ

  12. ςετޮ཰ͷҰൠతͳΞϓϩʔνɿଟஈࣜΤϥʔϓϧʔϑ σόοάͷૣ͞ͱྔ͸τϨʔυΦϑͳͷͰஈ֊తͳςετ͕๬·͍͠ͱ͍͏ߟ͑ํ ׳ΕΔʁJS ૉૣ͘ڀ໌ʂσόοάೖ໳ https://speakerdeck.com/orgachem/guan-reru-js- su-zao-kujiu-ming-debatuguru-men?slide=32 Kuniwak

  13. ҰൠతͳΞϓϩʔνɿଟஈࣜΤϥʔϓϧʔϑ σόοάͷૣ͞ͱྔ͸τϨʔυΦϑͳͷͰஈ֊తͳςετ͕๬·͍͠ͱ͍͏ߟ͑ํ ׳ΕΔʁJS ૉૣ͘ڀ໌ʂσόοάೖ໳ https://speakerdeck.com/orgachem/guan-reru-js- su-zao-kujiu-ming-debatuguru-men?slide=32 Kuniwak αʔόʔϨεͰ΋͓ͳ͡

  14. ςετޮ཰ͷҰൠతͳΞϓϩʔνɿଟஈࣜΤϥʔϓϧʔϑ σόοάͷૣ͞ͱྔ͸τϨʔυΦϑͳͷͰஈ֊తͳςετ͕๬·͍͠ͱ͍͏ߟ͑ํ ׳ΕΔʁJS ૉૣ͘ڀ໌ʂσόοάೖ໳ https://speakerdeck.com/orgachem/guan-reru-js- su-zao-kujiu-ming-debatuguru-men?slide=32 Kuniwak 5ZQF4DSJQUΛ ࢖͏ཧ༝͕͜Ε

  15. ҰൠతͳΞϓϩʔνɿଟஈࣜΤϥʔϓϧʔϑ σόοάͷૣ͞ͱྔ͸τϨʔυΦϑͳͷͰஈ֊తͳςετ͕๬·͍͠ͱ͍͏ߟ͑ํ ׳ΕΔʁJS ૉૣ͘ڀ໌ʂσόοάೖ໳ https://speakerdeck.com/orgachem/guan-reru-js- su-zao-kujiu-ming-debatuguru-men?slide=32 Kuniwak ࣍͸͜ͷதͷ ޮ཰Λߟ͑Δ

  16. ΞδΣϯμ ޮ཰ͷྑ͍ςετઓज़ ϢχοτςετͱΞϓϦέʔγϣϯઃܭ! E2EςετͱΞʔΩςΫνϟύλʔϯ ·ͱΊ

  17. Ϣχοτςετɿ-BNCEB'VODUJPO͸ԿͰ͔͢ʁ ೖग़ྗͷ͋Δؔ਺ʁ:&4͕ͩͦΕʹཹ·Βͳ͍ ֎෦͔ΒೖྗΛड͚औΓɺϏδωεϩδοΫΛࢪͯ͠ɺ
 ผͷαʔϏεΛݺͿ ΞϓϦέʔγϣϯͷ໾ׂΛ୲͏͜ͱ΋૿͖͑ͯͨ ΞϓϦέʔγϣϯͩͱ͢ΔͱɺઃܭΛߟ͑Δҙຯ͕͋Δ

  18. 4#(JGU༷ΪϑταʔϏεόοΫΤϯυͷҰ෦ ྫ͑͹νέοτ৘ใͷফࠐ -BNCEB'VODUJPO

  19. 4#(JGU༷ΪϑταʔϏεόοΫΤϯυͷҰ෦ ྫ͑͹νέοτ৘ใͷফࠐ -BNCEB'VODUJPO

  20. 4#(JGU༷ΪϑταʔϏεόοΫΤϯυͷҰ෦ ྫ͑͹νέοτ৘ใͷফࠐ -BNCEB'VODUJPO IBOEMFS &YDIBOHF,JOFTJT)BOEMFS

  21. 4#(JGU༷ΪϑταʔϏεόοΫΤϯυͷҰ෦ ྫ͑͹νέοτ৘ใͷফࠐ -BNCEB'VODUJPO IBOEMFS &YDIBOHF,JOFTJT)BOEMFS $MFBS5JDLFU6TF$BTF 7BMJEBUPST EPNBJO

  22. 4#(JGU༷ΪϑταʔϏεόοΫΤϯυͷҰ෦ ྫ͑͹νέοτ৘ใͷফࠐ -BNCEB'VODUJPO IBOEMFS &YDIBOHF,JOFTJT)BOEMFS $MFBS5JDLFU6TF$BTF $MFBS5JDLFU"QJ$BMMFS 7BMJEBUPST EPNBJO JOGSB

  23. 4#(JGU༷ΪϑταʔϏεόοΫΤϯυͷҰ෦ ྫ͑͹νέοτ৘ใͷফࠐ -BNCEB'VODUJPO IBOEMFS &YDIBOHF,JOFTJT)BOEMFS $MFBS5JDLFU6TF$BTF $MFBS5JDLFU"QJ$BMMFS DPNQPOFOU 7BMJEBUPST EPNBJO

    JOGSB
  24. 4#(JGU༷ΪϑταʔϏεόοΫΤϯυͷҰ෦ ྫ͑͹νέοτ৘ใͷফࠐ -BNCEB'VODUJPO IBOEMFS &YDIBOHF,JOFTJT)BOEMFS $MFBS5JDLFU6TF$BTF $MFBS5JDLFU"QJ$BMMFS DPNQPOFOU 7BMJEBUPST EPNBJO

    JOGSB ϨΠϠผɺػೳผʹ ίʔυΛ෼ྨ
  25. ΞϓϦέʔγϣϯઃܭΛखʹೖΕͨ αʔόʔϨεͷੈքʹ͍Ζ͍ΖͳσβΠϯύλʔϯ͕ྲྀ ༻Ͱ͖Δ )VNCMF0CKFDUύλʔϯ ϢχοτςετΛ࣮ߦ͢Δਓ͕ɺςετ͠ʹ͍͘ৼΔ෣͍ͱ ςετ͠΍͍͢ৼΔ෣͍Λ෼ྨ͢ΔͨΊʹੜ·Εͨ ֎քͱ΍ΓͱΓ͢ΔϨΠϠͷίʔυΛՄೳͳݶΓബ͘͢Δ

  26. )VNCMF0CKFDUύλʔϯͷద༻ IBOEMFS &YDIBOHF,JOFTJT)BOEMFS $MFBS5JDLFU6TF$BTF $MFBS5JDLFU"QJ$BMMFS DPNQPOFOU 7BMJEBUPST EPNBJO JOGSB ͜͜Λް͘ʂ

  27. )VNCMF0CKFDUύλʔϯͷద༻ IBOEMFS &YDIBOHF,JOFTJT)BOEMFS $MFBS5JDLFU6TF$BTF $MFBS5JDLFU"QJ$BMMFS DPNQPOFOU 7BMJEBUPST EPNBJO JOGSB ͜͜͸ബ͘ʂ

    ɾ+40/ͷม׵ ɾॲཧݺͼग़͠ ͘Β͍ʹͳΔΑ͏ؤுΔ
  28. ϐϡΞϩδοΫΛॆ࣮ͤ͞Ε͹ςετޮ཰61 ςετ͠ʹ͍͘ՕॴΛബͨ͘͜͠ͱͰɺϐϡΞϩδοΫ ͕ॆ࣮ͨ͠ ͜͜ʹରͯ͠Ϣχοτςετ͸Πϝʔδ͠΍͍͢ )VNCMF0CKFDUʹͨ͠ͱ͜Ζ͸͍ͬͨΜϞοΫͰ0, -PDBM4UBDL΍4".-PDBMͷಋೖ͸ͪΐͬͱ4501 ͜ΕΒͰ֬ೝͰ͖Δର৅͸͍·͸)VNCMFͳʢബ͍ʣͷͰɻ

  29. 5JQTϞοΫԽʹ͸+FTUͷTQZ0O ͕໾ཱͭ describe('Clear ticket use case', () => { test('run

    clear ticket', async (): Promise<void> => { const en = ExchangeNotification.of('123456'); const getTicketSpy = jest .spyOn(ticket, 'getTicket') .mockResolvedValue(searchTicket); const clearTicketSpy = jest .spyOn(ticket, 'clearTicket') .mockResolvedValue('Created:UT'); const result = await clearingTicketUseCase(en); expect(result).toEqual({ result: 'Created', }); expect(getTicketSpy).toHaveBeenCalledWith( en.ticketNo,en.shoriTime, ); expect(clearTicketSpy).toHaveBeenCalledWith(searchTicket); }); });
  30. 5JQTϞοΫԽʹ͸+FTUͷTQZ0O ͕໾ཱͭ describe('Clear ticket use case', () => { test('run

    clear ticket', async (): Promise<void> => { const en = ExchangeNotification.of('123456'); const getTicketSpy = jest .spyOn(ticket, 'getTicket') .mockResolvedValue(searchTicket); const clearTicketSpy = jest .spyOn(ticket, 'clearTicket') .mockResolvedValue('Created:UT'); const result = await clearingTicketUseCase(en); expect(result).toEqual({ result: 'Created', }); expect(getTicketSpy).toHaveBeenCalledWith( en.ticketNo,en.shoriTime, ); expect(clearTicketSpy).toHaveBeenCalledWith(searchTicket); }); }); ͜ΕͰϩʔΧϧ࣮ߦՄೳ
  31. 5JQTϐϡΞϩδοΫͷ໢ཏʹ͸1BSBNFUFSJ[FE describe('NotNullValidator', () => { test.each` name | value |

    expected ${'address'} | ${'tokyo'} | ${{ isValid: true }} ${'first name'} | ${''} | ${{ isValid: true }} ${'first name'} | ${null} | ${{ isValid: false }} `( 'input $value, validation result expected: $expected.isValid', ({ name, value, expected }) => { const suc = new NotNullValidator(name, value); expect(suc.validate()).toEqual({ isValid: expected.isValid, }); }, ); });
  32. 5JQTϐϡΞϩδοΫͷ໢ཏʹ͸1BSBNFUFSJ[FE describe('NotNullValidator', () => { test.each` name | value |

    expected ${'address'} | ${'tokyo'} | ${{ isValid: true }} ${'first name'} | ${''} | ${{ isValid: true }} ${'first name'} | ${null} | ${{ isValid: false }} `( 'input $value, validation result expected: $expected.isValid', ({ name, value, expected }) => { const suc = new NotNullValidator(name, value); expect(suc.validate()).toEqual({ isValid: expected.isValid, }); }, ); }); ೖྗɾظ଴஋ϖΞΛςʔϒϧܗࣜͰఆٛ
  33. Ϣχοτςετ·ͱΊ )VNCMF0CKFDUύλʔϯΛ࢖ͬͯϋϯυϥϨΠϠɺ ΠϯϑϥϨΠϠΛബ͘͢Δ ॆ࣮ͨ͠ϐϡΞϩδοΫʹରͯ͠ςετ͢Δ ͦͷࡍɺϞοΫԽͷͨΊͷTQZ0O έʔε໢ཏͷͨΊ ͷQBSBNFUFSJ[FEUFTUͰޮ཰ԽͰ͖Δ

  34. ΞδΣϯμ ޮ཰ͷྑ͍ςετઓज़ ϢχοτςετͱΞϓϦέʔγϣϯઃܭ E2EςετͱΞʔΩςΫνϟύλʔϯ! ·ͱΊ

  35. &&ςετͷఆٛʁ αʔϏεͷ࢓ࣄͿΓΛ֬ೝ͢ΔςετΛ&&ͱΈͳ͢ ࠓճͷ৔߹ͩͱσϓϩΠ͞Εͨ8FC"1*ͷ͜ͱ &&ྫɿ
 ϦΫΤετΛૹͬͨΒɺϨεϙϯε͕ฦͬͯ͘ΔͷͰɺͦΕ Λ֬ೝ͢Δ ϒϥ΢β͸࢖Θͳͯ͘΋0,

  36. ਖ਼ৗέʔεΛ&&ςετͰҰຊ௨͢ σϓϩΠͨ͠ΞϓϦέʔγϣϯʹରͯ͠ਖ਼ৗܥຊͰྑ͍ αʔόʔϨεΞϓϦέʔγϣϯͰ͸ͱͯͭ΋ͳ͍Ձ஋͕͋Δ )VNCMF0CKFDU͕ਖ਼ৗܥͻͱͭͰಈ͍͍ͯΔ "84ߏ੒ͷ௨Γਖ਼ৗܥͻͱͭͰಈ͍͍ͯΔ ಛʹޙऀ͸-PDBM4UBDLͰ΋֬ೝͰ͖ͳ͍ ͍ΘΏΔʮಈ࡞֬ೝʯΛςετίʔυʹམͱ͢

  37. 8FC"1*࢓ࣄͷύλʔϯ ϦΫΤετϨεϙϯε ϦΫΤετσʔλอଘ ϦΫΤετ֎෦αʔϏε

  38. 8FC"1*࢓ࣄͷύλʔϯ ϦΫΤετϨεϙϯε ϦΫΤετσʔλอଘ ϦΫΤετ֎෦αʔϏε ϨεϙϯεϘσΟΛ֬ೝ

  39. 8FC"1*࢓ࣄͷύλʔϯ ϦΫΤετϨεϙϯε describe('exchange API', (): void => { test('clear ticket',

    async (): void => { const exchangeNotificationUrl = 'http://ticket-system.jp/ticket/clear'; const request = { shori_id: '01234567890', }; const response = await Axios.post( exchangeNotificationUrl, qs.stringify(request), ); expect(response.data).toEqual('{ "response": "ok" }'); }); });
  40. 8FC"1*࢓ࣄͷύλʔϯ ϦΫΤετϨεϙϯε describe('exchange API', (): void => { test('clear ticket',

    async (): void => { const exchangeNotificationUrl = 'http://ticket-system.jp/ticket/clear'; const request = { shori_id: '01234567890', }; const response = await Axios.post( exchangeNotificationUrl, qs.stringify(request), ); expect(response.data).toEqual('{ "response": "ok" }'); }); }); ϨεϙϯεϘσΟΛ֬ೝ
  41. 8FC"1*࢓ࣄͷύλʔϯ ϦΫΤετϨεϙϯε ϦΫΤετσʔλอଘ ϦΫΤετ֎෦αʔϏε อଘ͞ΕͨσʔλΛ֬ೝ

  42. 8FC"1*࢓ࣄͷύλʔϯ ϦΫΤετσʔλอଘ describe('exchange API', (): void => { test('clear ticket',

    async (): void => { // Axios request … // const param = { TableName: 'ticket-info', Key: { shoriId: '01234567890' }, }; const dynamoData = await dynamo.getItem(param).promsie(); expect(dynamoData).toEqual({ shoriId: request.shori_id, clear: true, }); }); });
  43. 8FC"1*࢓ࣄͷύλʔϯ ϦΫΤετσʔλอଘ describe('exchange API', (): void => { test('clear ticket',

    async (): void => { // Axios request … // const param = { TableName: 'ticket-info', Key: { shoriId: '01234567890' }, }; const dynamoData = await dynamo.getItem(param).promsie(); expect(dynamoData).toEqual({ shoriId: request.shori_id, clear: true, }); }); }); อଘ͞ΕͨσʔλΛ֬ೝ
  44. 8FC"1*࢓ࣄͷύλʔϯ ϦΫΤετϨεϙϯε ϦΫΤετσʔλอଘ ϦΫΤετ֎෦αʔϏε ໰୊͸͜Ε

  45. ϦΫΤετ֎෦αʔϏείʔϧλΠϓ ࡞ͬͨ8FC"1*Ͱ׬݁͠ͳ͍͜ͱ͸βϥ 1BDUͰ$%$ςετͱ͍͏ख΋͋Δ͕ɺ·ͣ͸ࣗ෼͚ͨͪͩͰͰ͖Δ͜ ͱΛߟ͍͑ͨ ʮԶͨͪͷ੹೚ൣғ಺Ͱ͸ɺ΍Δ͜ͱ͸΍ͬͨʯͱ͍͏͜ͱ͕֬ೝͰ͖Ε ͹Α͍ ֎෦αʔϏεΛݺΜͩه࿥Λ࢒͓͖ͯ͠ɺͦͷه࿥͕ҙਤͲ͓ΓͰ͋Δ͜ ͱΛ֬ೝ ࣮૷ଆͷमਖ਼΋ඞཁ͕ͩɺ΍ΔՁ஋͸͋Δ Introduction

    - Pact https://docs.pact.io/
  46. ྫɿ4ʹอଘͨ͠ॲཧه࿥Λࢀরͯ֬͠ೝ͢Δ describe('exchange API', (): void => { test('clear ticket', async

    (): Promise<void> => { const exchangeNotificationUrl = 'http://ticket-system.jp/ticket/clear'; const request = { shori_id: '01234567890', }; const response = await Axios.post( exchangeNotificationUrl, qs.stringify(request), ); expect(response.data).toEqual('{ "response": "ok" }'); const param = { Bucket: 'ticket-info', Key: request.shori_id, }; const s3Obj = await s3.getObject(param).promsie(); const reocrd = JSON.parse(s3Obj.Body!.toString()); expect(reocrd).toEqual({ monitor: 'Created', }); }); });
  47. ྫɿ4ʹอଘͨ͠ॲཧه࿥Λࢀরͯ֬͠ೝ͢Δ describe('exchange API', (): void => { test('clear ticket', async

    (): Promise<void> => { const exchangeNotificationUrl = 'http://ticket-system.jp/ticket/clear'; const request = { shori_id: '01234567890', }; const response = await Axios.post( exchangeNotificationUrl, qs.stringify(request), ); expect(response.data).toEqual('{ "response": "ok" }'); const param = { Bucket: 'ticket-info', Key: request.shori_id, }; const s3Obj = await s3.getObject(param).promsie(); const reocrd = JSON.parse(s3Obj.Body!.toString()); expect(reocrd).toEqual({ monitor: 'Created', }); }); });
  48. ྫɿ4ʹอଘͨ͠ॲཧه࿥Λࢀরͯ֬͠ೝ͢Δ describe('exchange API', (): void => { test('clear ticket', async

    (): Promise<void> => { const exchangeNotificationUrl = 'http://ticket-system.jp/ticket/clear'; const request = { shori_id: '01234567890', }; const response = await Axios.post( exchangeNotificationUrl, qs.stringify(request), ); expect(response.data).toEqual('{ "response": "ok" }'); const param = { Bucket: 'ticket-info', Key: request.shori_id, }; const s3Obj = await s3.getObject(param).promsie(); const reocrd = JSON.parse(s3Obj.Body!.toString()); expect(reocrd).toEqual({ monitor: 'Created', }); }); }); ֎෦"1*͔Β$SFBUFE͕ ฦ͖ͬͯͨ͜ͱΛه࿥ɺ֬ೝ ※࣮ࡍʹ͸஗ԆͱϦτϥΠ͕ඞཁ
  49. &&ςετ·ͱΊ ਖ਼ৗܥΛҰຊ௨͢͜ͱͰಘΒΕΔ҆৺ײ͸େ͖͍ αʔόʔϨεΞϓϦέʔγϣϯͷύλʔϯʹ߹Θͤͯɺ ·ͣॻ͘ ͦͷޙɺΤϥʔϨεϙϯεΛॆ࣮ͤͨ͞Γɺอଘ͢Δ σʔλͷόϦΤʔγϣϯΛ૿΍ͤ͹ྑ͍

  50. ΞδΣϯμ ޮ཰ͷྑ͍ςετઓज़ ϢχοτςετͱΞϓϦέʔγϣϯઃܭ E2EςετͱΞʔΩςΫνϟύλʔϯ ·ͱΊ!

  51. ϢχοτςετҎ߱ͷΤϥʔϓϧʔϑΟϯά )VNCMF0CKFDU ͷϢχοτςετ ϐϡΞϩδοΫͷϢχοτςετ ਖ਼ৗܥͷ&&ςετ ҟৗܥΛؚΉ&&ςετ 6 5 & 

    & JSONม׵ϛε ϩδοΫߟྀ࿙Ε AWSߏ੒ϛε
 IAMϛε ࢓༷ͷෆ۩߹
  52. ϢχοτςετͷΞϓϩʔν )VNCMF0CKFDU ͷϢχοτςετ ϐϡΞϩδοΫͷϢχοτςετ ਖ਼ৗܥͷ&&ςετ ҟৗܥΛؚΉ&&ςετ 6 5 & 

    & JSONม׵ϛε ϩδοΫߟྀ࿙Ε AWSߏ੒ϛε
 IAMϛε ࢓༷ͷෆ۩߹ ςετ͠΍͍͢ϐϡΞϩδοΫͱͦΕҎ֎Λ෼ׂͨ͠
  53. ϢχοτςετͷΞϓϩʔν )VNCMF0CKFDU ͷϢχοτςετ ϐϡΞϩδοΫͷϢχοτςετ ਖ਼ৗܥͷ&&ςετ ҟৗܥΛؚΉ&&ςετ 6 5 & 

    & JSONม׵ϛε ϩδοΫߟྀ࿙Ε AWSߏ੒ϛε
 IAMϛε ࢓༷ͷෆ۩߹ +FTUͷػೳΛ׆༻ͯ͠ޮ཰ԽͰ͖Δ ɾspyOn() ʹΑΔϞοΫ ɾparameterized test
  54. &&ςετ )VNCMF0CKFDU ͷϢχοτςετ ϐϡΞϩδοΫͷϢχοτςετ ਖ਼ৗܥͷ&&ςετ ҟৗܥΛؚΉ&&ςετ 6 5 & 

    & JSONม׵ϛε ϩδοΫߟྀ࿙Ε AWSߏ੒ϛε
 IAMϛε ࢓༷ͷෆ۩߹ ·ͣ͸Ұຊ௨͢
  55. &&ςετ )VNCMF0CKFDU ͷϢχοτςετ ϐϡΞϩδοΫͷϢχοτςετ ਖ਼ৗܥͷ&&ςετ ҟৗܥΛؚΉ&&ςετ 6 5 & 

    & JSONม׵ϛε ϩδοΫߟྀ࿙Ε AWSߏ੒ϛε
 IAMϛε ࢓༷ͷෆ۩߹ ύλʔϯʹ߹Θ࣮ͤͯࢪ ɾϦΫΤετ-Ϩεϙϯε ɾϦΫΤετ-σʔλอଘ ɾϦΫΤετ-αʔϏείʔϧ
  56. ·ͱΊ )VNCMF0CKFDU ͷϢχοτςετ ϐϡΞϩδοΫͷϢχοτςετ ਖ਼ৗܥͷ&&ςετ ҟৗܥΛؚΉ&&ςετ 6 5 & 

    & JSONม׵ϛε ϩδοΫߟྀ࿙Ε AWSߏ੒ϛε
 IAMϛε ࢓༷ͷෆ۩߹ ͜ͷ;ͨͭɺίεύ˕Ͱ͸͡Ί΍͍͢ʂ˞΋ͪΖΜ࠷ޙ͸શ෦΍Δ
  57. ͍͞͝ʹ ιϑτ΢ΣΞςετ͸ɺ਺ֶతূ໌Ͱ͸ͳ͘Պֶత൓ূ ʮগͳ͘ͱ΋͜ͷέʔεͰ͸ςετ͕ࣦഊ͠ͳ͍ʯ։ൃऀࣗ਎ͷͨ ΊͷূڌूΊ࡞ۀɻͲ͜·ͰԿΛ΍Δ͔ʁݶΒΕͨ࣌ؒͷͳ͔Ͱམ ͱ͠ॴΛܾΊͯޮ཰Λ্͛Δඞཁ͕͋Δ ۃ୺ͳ࿩ɺϝϯς༧ఆͷͳ͍ɺͨͩͻͱͭͷॲཧ͕͢Ͱʹόάͳ͘ ಈ͍͍ͯΔͷͳΒɺվΊͯςετΛॻ͘ඞཁ͸ͳ͍ Ͱ΋Ϗδωε͸੒௕͢Δ͠ɺ/PEFKTͷόʔδϣϯ΋্͕Δ͠ɺ 3%41SPYZ΋ొ৔͢ΔɻपΓ͕มԽ͢Δɻςετ͸ඞཁʹͳΔɻ ͦͷͱ͖ʹखΛ෇͚ΔͨΊͷࢀߟʹͳΕ͹޾͍Ͱ͢

  58. େࠓ೔ࢀߟʹ͍͍ͤͯͨͩͨ͞৘ใ ࿨ా୎ਓ, 2017/6/2 AWS Dev Day Tokyo 2017 IUUQTTQFBLFSEFDLDPNUXBEBUFTUBCMFMBNCEBXPSLJOHF⒎FDUJWFMZXJUIMFHBDZMBNCEB Testable

    Lambda Working Effectivery with Legacy Lambda Clean Architecture ୡਓʹֶͿιϑτ΢ΣΞͷ ߏ଄ͱઃܭ Robert C. Martin ςετۦಈ։ൃ Kent Beck, ࿨ా୎ਓ