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

StorybookのUI Testing Handbookを読んだ

StorybookのUI Testing Handbookを読んだ

Shingo Yamazaki

January 19, 2022
Tweet

More Decks by Shingo Yamazaki

Other Decks in Technology

Transcript

  1. ࣗݾ঺հ Shingo Yamazaki • גࣜձࣾϩάϥε • ࡢ೥9݄͔Β University of the

    People Ͱ 
 ίϯϐϡʔλʔαΠΤϯεษڧத zaki-yama zaki___yama
  2. UI Testing Handbook • Storybook ͕ఏڙ͍ͯ͠ΔνϡʔτϦΞϧίϯςϯπͷͻͱͭ • ࡢ೥12݄ʹެ։͞Εͨ • Twilio,

    Adobe, Shopify ͳͲɺStorybookίϛϡχςΟͷ10ݸͷνʔϜ ΛϦαʔνͯ͠ಘΒΕͨ஌ݟΛ·ͱΊͨ΋ͷʢ”Introduction” ΑΓʣ
  3. ςετ͢΂͖UIͷಛ௃ • Visual • ݟͨ໨ • Interaction • ΫϦοΫ΍ϢʔβʔೖྗͳͲͷΠϕϯτ͕ద੾ʹϋϯυϦϯά͞ΕΔ͔ •

    Accessibility • ΞΫηγϏϦςΟ • User fl ow • ෳ਺ͷίϯϙʔωϯτʹ·͕ͨͬͯ׬݁͢ΔϢʔβʔૢ࡞͕ظ଴௨Γߦ͑Δ͔
  4. ͦΕͧΕΛ࣮ݱ͢ΔͨΊͷπʔϧʗϥΠϒϥϦ • Visual • ݟͨ໨ … Chromatic • Interaction …

    Jest & Testing Library • ΫϦοΫ΍ϢʔβʔೖྗͳͲͷΠϕϯτ͕ద੾ʹϋϯυϦϯά͞ΕΔ͔ • Accessibility • ΞΫηγϏϦςΟ … Axe • User fl ow … Cypress (or Playwright, Selenium) • ෳ਺ͷίϯϙʔωϯτʹ·͕ͨͬͯ׬݁͢ΔϢʔβʔૢ࡞͕ظ଴௨Γߦ͑Δ͔
  5. Visual • Chromatic Λ࢖ͬͨ Visual Regression Testing ͷ঺հ • Storybook

    ͷ಺༰Λը૾ͰΩϟϓνϟ͠ɺίϛοτؒͰࠩ෼͕ͳ͍ ͔νΣοΫ͢Δ
  6. Interaction import { render, waitFor, cleanup, within, fireEvent, } from

    "@testing-library/react"; import { composeStories } from "@storybook/testing-react"; import * as stories from "./InboxScreen.stories"; describe("InboxScreen", () => { const { Default } = composeStories(stories); it("should pin a task", async () => { const { queryByText, getByRole } = render(<Default />); await waitFor(() => { expect(queryByText("You have no tasks")).not.toBeInTheDocument(); }); const getTask = () => getByRole("listitem", { name: "Export logo" }); const pinButton = within(getTask()).getByRole("button", { name: "pin" }); fireEvent.click(pinButton); const unpinButton = within(getTask()).getByRole("button", { name: "unpin", }); expect(unpinButton).toBeInTheDocument(); });
  7. Interaction import { render, waitFor, cleanup, within, fireEvent, } from

    "@testing-library/react"; import { composeStories } from "@storybook/testing-react"; import * as stories from "./InboxScreen.stories"; describe("InboxScreen", () => { const { Default } = composeStories(stories); it("should pin a task", async () => { const { queryByText, getByRole } = render(<Default />); await waitFor(() => { expect(queryByText("You have no tasks")).not.toBeInTheDocument(); }); const getTask = () => getByRole("listitem", { name: "Export logo" }); const pinButton = within(getTask()).getByRole("button", { name: "pin" }); fireEvent.click(pinButton); const unpinButton = within(getTask()).getByRole("button", { name: "unpin", }); expect(unpinButton).toBeInTheDocument(); }); 4UPSZΛΠϯϙʔτ͠ɺ ͦͷ··ςετέʔεͱͯ͠ར༻͢Δ
  8. Accessibility: jest-axe import { axe, toHaveNoViolations } from "jest-axe"; import

    { composeStories } from "@storybook/testing-react"; import * as stories from "./InboxScreen.stories"; expect.extend(toHaveNoViolations); describe("InboxScreen", () => { ... const { Default } = composeStories(stories); // Run axe it("should have no accessibility violations", async () => { const { container, queryByText } = render(<Default />); await waitFor(() => { expect(queryByText("You have no tasks")).not.toBeInTheDocument(); }); const results = await axe(container); expect(results).toHaveNoViolations(); }); UP)BWF/P7JPMBUJPOT Λݺͼग़͚ͩ͢
  9. User fl ow • ͍ΘΏΔE2Eςετ • 2ͭͷબ୒ࢶ͕ߟ͑ΒΕΔ 1. όοΫΤϯυ·ͰؚΊͨ׬શͳςετ؀ڥΛ࢖͏ʢO’ReillyνʔϜ͸ͬͪ͜ʣ 2.

    όοΫΤϯυ͸ϞοΫ͠ɺϑϩϯτΤϯυͷΈʢTwilioνʔϜ͸ͬͪ͜ʣ • νϡʔτϦΞϧͰ͸ޙऀɻCypress Λ࢖͏ • “ϩάΠϯը໘Ͱਖ਼͍͠Ϣʔβʔ໊&ύεϫʔυΛೖྗͨ͠ΒɺλεΫҰཡ͕։͚Δ” ͱ͍͏ςετ έʔε • ͜͜Ͱ΋ Story Λςετέʔεͱͯ͠࠶ར༻
  10. ͜͏͍ͬͨ͜ͱֶ͕΂ͯΑ͔ͬͨ☺ • ֤छςετ؍఺ͱͦΕΛ࣮ݱ͢ΔͨΊͷ۩ମతͳϥΠϒϥϦ • Chromatic, Axe, Cypress, etc. • StorybookΛத৺ʹஔ͍࣮ͨ༻తͳϫʔΫϑϩʔ

    • StoryΛJest΍Cypressͷςετʹ΋࠶ར༻͢Δɺͱ͍͏ߟ͑ํ • GitHub ActionsʹΑΔCIʹ͍ͭͯ΋঺հ͞Ε͍ͯΔʢ”Automate” ͷষʣ • ࣮ࡍʹίʔυॻ͍ͯಈ͘΋ͷΛݟͳ͕Βֶ΂ͨͷ΋ݸਓతʹ͸Α͔ͬͨ
  11. Ҿ͖ଓ͖Θ͔ΒΜ😭 • ͦΕͧΕͷςετΛͲ͏͍͏ج४Ͱ૊Έ߹ΘͤͯɺΞϓϦέʔγϣϯશମͱ ͯ͠ͷ඼࣭Λ୲อ͢Δ͔ • ಛʹ Interaction ͱ User fl

    ow (E2E) ͷ੗Έ෼͚ • “Given this trade-off, most teams use a hybrid approach to balance effort and value. E2E tests are limited to only critical user fl ows and interaction tests are used to verify all other behavior.” • όοΫΤϯυΛϞοΫͨ͠E2Eςετ͸ͳΜͱͳ͘த్൒୺ͳͷͰ͸…ͱ͍ ͏ײ͡΋͢Δ