腐らないUIテストのためのStorybook + Storyshots / #react_kyoto v0.3.0

腐らないUIテストのためのStorybook + Storyshots / #react_kyoto v0.3.0

React.kyoto v0.3.0 ( https://react-kyoto.connpass.com/event/137847/ )でStorybookとStoryshotsを使ったコンポーネントのUIテストについて話しました。
質問・不備・マサカリ等はTwitterにてメンションつけてもらえると嬉しいです。
Twitter: https://twitter.com/shisama_

5cf7e9533a457726cd51232e06c1da9a?s=128

Masashi Hirano

July 19, 2019
Tweet

Transcript

  1. ෗Βͳ͍UIςετͷͨΊͷ Storybook + Storyshots react.kyoto v0.3.0 ฏ໺ণ࢜ @shisama

  2. ฏ໺ ণ࢜ / Masashi Hirano @shisama_ @shisama Node.js Core Collaborator

    ؔ੢NodeֶԂOrganizer
  3. એ఻

  4. Agenda • ίϯϙʔωϯτҰཡΛ֬ೝ͢Δ(Storybook) • ίϯϙʔωϯτͷڍಈΛ֬ೝ͢Δ(Storybook) • εφοϓγϣοτςετΛߦ͏(Storyshots) • ϝϯςφϯεҡ࣋ՄೳͳUIςετΛ
 Storybook

    + StoryshotsͰ࣮ݱ͢Δ
  5. Ұ౓͸ࢥͬͨ͜ͱ͕ͳ͍Ͱ͔͢…? • ૿͑͗ͨ͢ίϯϙʔωϯτΛҰཡͰݟ͍ͨ • ౉͢propsʹԠͨ͡දࣔΛνΣοΫ͍ͨ͠ • ݸʑͷίϯϙʔωϯτͷදࣔΛνΣοΫ͠ͳ͕ ΒϨϏϡʔ͕͍ͨ͠

  6. https://storybook.js.org/

  7. Storybook • ίϯϙʔωϯτΛҰཡදࣔ͢Δπʔϧ • ݸʑͷίϯϙʔωϯτͷද͕ࣔݟΕΔ • ౉͢propsʹԠͨ͡ද͕ࣔݟΕΔ • React, Vue,

    AngularʹରԠ
  8. None
  9. ίϯϙʔωϯτ͕Ұཡ දࣔ͞ΕΔ ֤ίϯϙʔωϯτͷςετέʔε ίϯϙʔωϯτͷදࣔ݁Ռ

  10. import React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

    import { faHeart } from '@fortawesome/free-regular-svg-icons'; type Props = { count: number; onClick: React.MouseEventHandler; }; export const Like = ({ count, onClick }: Props) => { return ( <> <div onClick={onClick}> <FontAwesomeIcon icon={faHeart} /> {count} </div> </> ); }; e.g. Likeίϯϙʔωϯτ
  11. Storybookಋೖ • @storybook/cliΛ࢖͏ • BabelͷઃఆΛߦ͏ • Storybook༻ͷwebpack.config.jsΛ࡞੒ • StorybookͷઃఆϑΝΠϧΛฤू

  12. @storybook/cli • npx -p @storybook/cli sb init --type react •

    StorybookͷઃఆΛࣗಈͰߦͬͯ͘ΕΔCLI • npm-scripts௥Ճ • StorybookͷઃఆϑΝΠϧ࡞੒ • Storybookͷαϯϓϧίʔυ࡞੒
  13. @storybook/cli TUPSZCPPLͷઃఆϑΝΠϧ TUPSZCPPLͷίʔυ JOEFYTUPSJFTKTαϯϓϧ

  14. Babelͷઃఆ • Storybook͸BabelΛ࢖͍ͬͯΔ • TypeScript΍React༻ʹpresetͳͲ͕ඞཁ • @babel/core • @babel/preset-typescript •

    @babel/preset-react
  15. module.exports = ({ config }) => { config.module.rules.push({ test: /\.tsx?$/,

    use: [ { loader: require.resolve('ts-loader') }, { loader: require.resolve('react-docgen-typescript-loader') } ] }); config.resolve.extensions.push('.ts', '.tsx'); return config; }; .storybook/webpack.config.js OQNJ%UTMPBEFS OQNJ%SFBDU EPDHFOUZQFTDSJQU
  16. import { configure } from '@storybook/react'; // ͜ͷrequire.contextͰಡΈࠐΉର৅ͷ֦ுࢠΛ.js͔Β.tsxʹฤू // const

    req = require.context('../stories', true, /\.stories\.js?$/); const req = require.context('../stories', true, /\.stories\.tsx?$/); function loadStories() { req.keys().forEach(filename => req(filename)); } configure(loadStories, module); .storybook/config.js ॳظ஋Ͱ͸KTΛࢦఆ͍ͯ͠Δ ͷͰUTY ͳͲʹมߋ
  17. import React from "react"; import { storiesOf } from "@storybook/react";

    import { Like } from "../src/components/Like"; storiesOf("Like", module).add("0", () => ( <Like count={0} onClick={() => {}} /> )).add("1", () => ( <Like count={1} onClick={() => {}} /> )); Storybook༻ͷίʔυΛॻ͘ FHDPVOU͕AAͱAAͷ৔߹ ͷTUPSJFTΛ௥Ճ͢Δ
  18. ࣮ߦ (npm run storybook)

  19. None
  20. None
  21. Storybook՝୊఺ • ίϯϙʔωϯτΛमਖ਼ͨ͠ͱ͖ʹStorybookͷ ίʔυ͸ޙճ͠͞Εϝϯς͞Εͳ͘ͳ͍ͬͯ͘ • ࣗಈςετ͢Δ΋ͷͰ͸ͳ͍ͨΊ໨ࢹͷνΣο Ϋ͕ඞཁ

  22. https://github.com/storybookjs/storybook/tree/master/addons/storyshots/storyshots-core

  23. Storyshots • StorybookͷίʔυΛ࢖͍ࣗಈςετ(εφοϓ γϣοτςετ)Λߦ͏ • ίϯϙʔωϯτΛमਖ਼࣌ʹStorybookͷίʔυ ΋मਖ਼͠ͳ͍ͱεφοϓγϣοτςετ͕ࣦഊ ͢Δ

  24. Storyshotsಋೖ • StoryshotsΠϯετʔϧ • JestΠϯετʔϧ • Jestͷઃఆ • Babelͷઃఆ

  25. StoryshotsΠϯετʔϧ • StoryshotsΛΠϯετʔϧ • @storybook/addon-storyshots • Storyshotsͷ࣮ߦʹඞཁͳύοέʔδΛΠϯε τʔϧ • babel-plugin-require-context-hook

    • react-test-renderer
  26. JestΠϯετʔϧ • jestͱbabel-jestΛΠϯετʔϧ • babel-jestͰ͸ͳ͘ts-jestΛ࢖͏ͱ࣮ߦ࣌ʹܕ νΣοΫ΋ͯ͘͠ΕΔ • babel-jestͷํ͕ಋೖ͸؆୯ • ܕνΣοΫ͸tscͰߦ͏

    • λεΫ͸ࡉ෼Խͯ͠ฒྻॲཧͰ͖ΔΑ͏ʹ
  27. Jestͷઃఆ • Jest࣮ߦʹඞཁͳsetupFilesΛ࡞੒ • εφοϓγϣοτςετͷίʔυΛ࡞੒ • jest.config.jsΛ࡞੒

  28. Jestͷઃఆ - setupFiles࡞੒ // .jest/register-context.ts import registerRequireContextHook from “babel-plugin- require-context-hook/register";

    registerRequireContextHook();
  29. Jestͷઃఆ - ςετϑΝΠϧ࡞੒ // stories/__tests__/Like.test.ts import initStoryshots from "@storybook/addon-storyshots"; initStoryshots();

  30. Jestͷઃఆ - jest.config.js࡞੒ module.exports = { setupFiles: ["<rootDir>/.jest/register-context.ts"], testMatch: ["<rootDir>/stories/__tests__/*.test.ts"]

    }; ϑΝΠϧͷ৔ॴ͸೚ҙ
  31. Babelͷઃఆ { "presets": [ [ "@babel/preset-env", { "targets": { "node":

    "current" } } ], "@babel/preset-typescript", "@babel/preset-react" ], "env": { "test": { "plugins": ["require-context-hook"] } } } KFTUDPOpHKTͷઃఆϑΝΠ ϧͷͨΊʹQSFTFUFOWΛΠ ϯετʔϧ UFTUͷͱ͖͚ͩSFRVJSF DPOUFYUIPPL͕ݺ͹ΕΔΑ ͏ʹ͓ͯ͘͠
  32. ࣮ߦ (npx jest) 4OBQTIPUϑΝΠϧ͕࡞੒͞ Εͨ

  33. ࣮ߦ݁Ռ (εφοϓγϣοτϑΝΠϧ) 4OBQTIPUϑΝΠϧ͕࡞੒͞ Εͨ

  34. ࣮ߦ݁Ռ (εφοϓγϣοτϑΝΠϧ)

  35. ίϯϙʔωϯτΛमਖ਼ͨ͠ͱ͖ ྫ͑͹ɺQSPQTͷ໊લ͕ม Θͬͨͱ͖

  36. ࣮ߦ (npx jest) લճςετ͔࣌Β݁Ռ͕ม ΘͬͨͨΊςετࣦഊ

  37. Storybook՝୊఺͕ղܾ • ίϯϙʔωϯτΛमਖ਼ͨ͠ͱ͖ʹStorybookͷίʔυ͸ ޙճ͠͞Εϝϯς͞Εͳ͘ͳ͍ͬͯ͘
 ⇛Storybookͷίʔυ΋ม͑ͳ͍ͱςετ͕௨Βͳ͍ͷ ͰϝϯςඞਢͱͳΔ
 • ࣗಈςετ͢Δ΋ͷͰ͸ͳ͍ͨΊ໨ࢹͷνΣοΫ͕ඞཁ
 ⇛テスト自動化

  38. ·ͱΊ • StorybookͰίϯϙʔωϯτ͝ͱͷUIΛνΣο ΫͰ͖Δ • StoryshotsͰςετࣗಈԽ͠ɺStorybookΛ෗Β ͞ͳ͍Α͏ʹ͢Δ

  39. https://github.com/shisama/react-storybook-storyshots-example ࠓճͷσϞ

  40. Thanks. @shisama_ shisama