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

Yusuke Wada
December 14, 2019

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

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

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

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

Yusuke Wada

December 14, 2019
Tweet

More Decks by Yusuke Wada

Other Decks in Technology

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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, ࿨ా୎ਓ

    View Slide

  6. ࣗݾ঺հ
    ࿨ా༞հ

    $9ࣄۀຊ෦

    αʔόʔϨεΤϯδχΞ
    XBEEZ@V
    ٕज़ॻయ7

    View Slide

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

    View Slide

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

    View Slide

  9. ൃදͷ໨ඪ
    ςετίʔυΛॻ͘ϋʔυϧΛԼ͛Δ͜ͱ
    ͦͷͨΊʹɿ
    αʔόʔϨεͰlίεύͷྑ͍zςετઓज़Λ੔ཧ͢Δ

    Ϣχοτςετɾ&&ςετ͕ର৅
    ͍ͭͰʹɿ
    αʔόʔϨεΞϓϦέʔγϣϯ։ൃʹ໾ཱͭ

    +FTUͷ5JQTΛ঺հ͢Δ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  17. Ϣχοτςετɿ-BNCEB'VODUJPO͸ԿͰ͔͢ʁ
    ೖग़ྗͷ͋Δؔ਺ʁ:&4͕ͩͦΕʹཹ·Βͳ͍
    ֎෦͔ΒೖྗΛड͚औΓɺϏδωεϩδοΫΛࢪͯ͠ɺ

    ผͷαʔϏεΛݺͿ
    ΞϓϦέʔγϣϯͷ໾ׂΛ୲͏͜ͱ΋૿͖͑ͯͨ
    ΞϓϦέʔγϣϯͩͱ͢ΔͱɺઃܭΛߟ͑Δҙຯ͕͋Δ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  27. )VNCMF0CKFDUύλʔϯͷద༻
    IBOEMFS
    &YDIBOHF,JOFTJT)BOEMFS
    $MFBS5JDLFU6TF$BTF
    $MFBS5JDLFU"QJ$BMMFS
    DPNQPOFOU
    7BMJEBUPST
    EPNBJO
    JOGSB
    ͜͜͸ബ͘ʂ
    ɾ+40/ͷม׵
    ɾॲཧݺͼग़͠
    ͘Β͍ʹͳΔΑ͏ؤுΔ

    View Slide

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

    View Slide

  29. 5JQTϞοΫԽʹ͸+FTUͷTQZ0O
    ͕໾ཱͭ
    describe('Clear ticket use case', () => {
    test('run clear ticket', async (): Promise => {
    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);
    });
    });

    View Slide

  30. 5JQTϞοΫԽʹ͸+FTUͷTQZ0O
    ͕໾ཱͭ
    describe('Clear ticket use case', () => {
    test('run clear ticket', async (): Promise => {
    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);
    });
    });
    ͜ΕͰϩʔΧϧ࣮ߦՄೳ

    View Slide

  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,
    });
    },
    );
    });

    View Slide

  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,
    });
    },
    );
    });
    ೖྗɾظ଴஋ϖΞΛςʔϒϧܗࣜͰఆٛ

    View Slide

  33. Ϣχοτςετ·ͱΊ
    )VNCMF0CKFDUύλʔϯΛ࢖ͬͯϋϯυϥϨΠϠɺ
    ΠϯϑϥϨΠϠΛബ͘͢Δ
    ॆ࣮ͨ͠ϐϡΞϩδοΫʹରͯ͠ςετ͢Δ
    ͦͷࡍɺϞοΫԽͷͨΊͷTQZ0O
    έʔε໢ཏͷͨΊ
    ͷQBSBNFUFSJ[FEUFTUͰޮ཰ԽͰ͖Δ

    View Slide

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

    View Slide

  35. &&ςετͷఆٛʁ
    αʔϏεͷ࢓ࣄͿΓΛ֬ೝ͢ΔςετΛ&&ͱΈͳ͢
    ࠓճͷ৔߹ͩͱσϓϩΠ͞Εͨ8FC"1*ͷ͜ͱ
    &&ྫɿ

    ϦΫΤετΛૹͬͨΒɺϨεϙϯε͕ฦͬͯ͘ΔͷͰɺͦΕ
    Λ֬ೝ͢Δ
    ϒϥ΢β͸࢖Θͳͯ͘΋0,

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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" }');
    });
    });

    View Slide

  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" }');
    });
    });
    ϨεϙϯεϘσΟΛ֬ೝ

    View Slide

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

    View Slide

  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,
    });
    });
    });

    View Slide

  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,
    });
    });
    });
    อଘ͞ΕͨσʔλΛ֬ೝ

    View Slide

  44. 8FC"1*࢓ࣄͷύλʔϯ
    ϦΫΤετϨεϙϯε
    ϦΫΤετσʔλอଘ
    ϦΫΤετ֎෦αʔϏε
    ໰୊͸͜Ε

    View Slide

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

    View Slide

  46. ྫɿ4ʹอଘͨ͠ॲཧه࿥Λࢀরͯ֬͠ೝ͢Δ
    describe('exchange API', (): void => {
    test('clear ticket', async (): Promise => {
    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',
    });
    });
    });

    View Slide

  47. ྫɿ4ʹอଘͨ͠ॲཧه࿥Λࢀরͯ֬͠ೝ͢Δ
    describe('exchange API', (): void => {
    test('clear ticket', async (): Promise => {
    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',
    });
    });
    });

    View Slide

  48. ྫɿ4ʹอଘͨ͠ॲཧه࿥Λࢀরͯ֬͠ೝ͢Δ
    describe('exchange API', (): void => {
    test('clear ticket', async (): Promise => {
    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͕
    ฦ͖ͬͯͨ͜ͱΛه࿥ɺ֬ೝ
    ※࣮ࡍʹ͸஗ԆͱϦτϥΠ͕ඞཁ

    View Slide

  49. &&ςετ·ͱΊ
    ਖ਼ৗܥΛҰຊ௨͢͜ͱͰಘΒΕΔ҆৺ײ͸େ͖͍
    αʔόʔϨεΞϓϦέʔγϣϯͷύλʔϯʹ߹Θͤͯɺ
    ·ͣॻ͘
    ͦͷޙɺΤϥʔϨεϙϯεΛॆ࣮ͤͨ͞Γɺอଘ͢Δ
    σʔλͷόϦΤʔγϣϯΛ૿΍ͤ͹ྑ͍

    View Slide

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

    View Slide

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

    &
    JSONม׵ϛε
    ϩδοΫߟྀ࿙Ε
    AWSߏ੒ϛε

    IAMϛε ࢓༷ͷෆ۩߹

    View Slide

  52. ϢχοτςετͷΞϓϩʔν
    )VNCMF0CKFDU
    ͷϢχοτςετ
    ϐϡΞϩδοΫͷϢχοτςετ
    ਖ਼ৗܥͷ&&ςετ ҟৗܥΛؚΉ&&ςετ
    6
    5
    &

    &
    JSONม׵ϛε
    ϩδοΫߟྀ࿙Ε
    AWSߏ੒ϛε

    IAMϛε ࢓༷ͷෆ۩߹
    ςετ͠΍͍͢ϐϡΞϩδοΫͱͦΕҎ֎Λ෼ׂͨ͠

    View Slide

  53. ϢχοτςετͷΞϓϩʔν
    )VNCMF0CKFDU
    ͷϢχοτςετ
    ϐϡΞϩδοΫͷϢχοτςετ
    ਖ਼ৗܥͷ&&ςετ ҟৗܥΛؚΉ&&ςετ
    6
    5
    &

    &
    JSONม׵ϛε
    ϩδοΫߟྀ࿙Ε
    AWSߏ੒ϛε

    IAMϛε ࢓༷ͷෆ۩߹
    +FTUͷػೳΛ׆༻ͯ͠ޮ཰ԽͰ͖Δ
    ɾspyOn() ʹΑΔϞοΫ
    ɾparameterized test

    View Slide

  54. &&ςετ
    )VNCMF0CKFDU
    ͷϢχοτςετ
    ϐϡΞϩδοΫͷϢχοτςετ
    ਖ਼ৗܥͷ&&ςετ ҟৗܥΛؚΉ&&ςετ
    6
    5
    &

    &
    JSONม׵ϛε
    ϩδοΫߟྀ࿙Ε
    AWSߏ੒ϛε

    IAMϛε ࢓༷ͷෆ۩߹
    ·ͣ͸Ұຊ௨͢

    View Slide

  55. &&ςετ
    )VNCMF0CKFDU
    ͷϢχοτςετ
    ϐϡΞϩδοΫͷϢχοτςετ
    ਖ਼ৗܥͷ&&ςετ ҟৗܥΛؚΉ&&ςετ
    6
    5
    &

    &
    JSONม׵ϛε
    ϩδοΫߟྀ࿙Ε
    AWSߏ੒ϛε

    IAMϛε ࢓༷ͷෆ۩߹
    ύλʔϯʹ߹Θ࣮ͤͯࢪ
    ɾϦΫΤετ-Ϩεϙϯε
    ɾϦΫΤετ-σʔλอଘ
    ɾϦΫΤετ-αʔϏείʔϧ

    View Slide

  56. ·ͱΊ
    )VNCMF0CKFDU
    ͷϢχοτςετ
    ϐϡΞϩδοΫͷϢχοτςετ
    ਖ਼ৗܥͷ&&ςετ ҟৗܥΛؚΉ&&ςετ
    6
    5
    &

    &
    JSONม׵ϛε
    ϩδοΫߟྀ࿙Ε
    AWSߏ੒ϛε

    IAMϛε ࢓༷ͷෆ۩߹
    ͜ͷ;ͨͭɺίεύ˕Ͱ͸͡Ί΍͍͢ʂ˞΋ͪΖΜ࠷ޙ͸શ෦΍Δ

    View Slide

  57. ͍͞͝ʹ
    ιϑτ΢ΣΞςετ͸ɺ਺ֶతূ໌Ͱ͸ͳ͘Պֶత൓ূ
    ʮগͳ͘ͱ΋͜ͷέʔεͰ͸ςετ͕ࣦഊ͠ͳ͍ʯ։ൃऀࣗ਎ͷͨ
    ΊͷূڌूΊ࡞ۀɻͲ͜·ͰԿΛ΍Δ͔ʁݶΒΕͨ࣌ؒͷͳ͔Ͱམ
    ͱ͠ॴΛܾΊͯޮ཰Λ্͛Δඞཁ͕͋Δ
    ۃ୺ͳ࿩ɺϝϯς༧ఆͷͳ͍ɺͨͩͻͱͭͷॲཧ͕͢Ͱʹόάͳ͘
    ಈ͍͍ͯΔͷͳΒɺվΊͯςετΛॻ͘ඞཁ͸ͳ͍
    Ͱ΋Ϗδωε͸੒௕͢Δ͠ɺ/PEFKTͷόʔδϣϯ΋্͕Δ͠ɺ
    3%41SPYZ΋ొ৔͢ΔɻपΓ͕มԽ͢Δɻςετ͸ඞཁʹͳΔɻ
    ͦͷͱ͖ʹखΛ෇͚ΔͨΊͷࢀߟʹͳΕ͹޾͍Ͱ͢

    View Slide

  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, ࿨ా୎ਓ

    View Slide