【フロントエンド開発のためのテスト入門】読んだ備忘録
自分用に フロントエンド開発のためのテスト入門 を読んだ備忘録を残しておきます。
以下、自分が付箋をはってた箇所をピックアップします。
備忘録
2-4 テスト戦略モデル
テストピラミッドは有名で何度も聞いたことがあった。
今回紹介されていたのはテスティングトロフィー型というものだった。
- Testing Libraryの開発者であるKent氏が提唱するテスト戦略モデルです。
- 効率よくテストを書くよりも、正しくソフトウェアが動作することを保証することが大切
- ユニットテストよりもインテグレーションテストを充実させよう!!
- 単体でなく結合させてソフトウェアを提供するので、そこの品質を補償しなければならない。
- 問題なくソフトウェアを提供できていれば単体にバグが潜んでても問題ない(暴論)
今の現場でもユニットテストより結合テストを大切にしていたが、何故そうしているのか説明できるようになった気がします。
第5章 UIコンポーネントテスト
ここで登場したのが Webアクセシビリティ(web-a11y) です。
ブラウザの支援技術を活用してシステムを使えるのか?というものですが、人生で1度も気にしたことがありませんでした。
- testing-library でUIテストを実装できます。
- 基本原則で「暗黙的なロール」も含めたクエリーを優先的に使用することを推奨しています。
参考: Testing Library Queries Priority
以下ではロール"button"を指定しているが、コンポーネント側では明示的に指定していない。
// https://github.com/frontend-testing-book/unittest/tree/main/src/05/03 test("ボタンの表示", () => { render(<Form name="taro" />); expect(screen.getByRole("button")).toBeInTheDocument(); });
ユーザ操作に近いシュミレートができる testing-library/user-event が特に良さそうだった。
// https://github.com/frontend-testing-book/unittest/blob/main/src/05/05/InputAccount.test.tsx import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { InputAccount } from "./InputAccount"; const user = userEvent.setup(); test("メールアドレス入力欄", async () => { render(<InputAccount />); // 入力欄を取得 const textbox = screen.getByRole("textbox", { name: "メールアドレス" }); const value = "taro.tanaka@example.com"; // 値を入力 await user.type(textbox, value); // 検証 expect(screen.getByDisplayValue(value)).toBeInTheDocument(); });
5-7 非同期処理を含むUIコンポーネントテスト
Arrange-Act-Assert(AAA)パターン
- 準備・実行・検証の3パターンでテストコードを構成する書き方
- 普段自分が意識している Given-When-Then と同じだと思う
test("成功時「登録しました」が表示される", async () => { // Arrange(準備) const mockFn = mockPostMyAddress(); render(<RegisterAddress />); // Act(実行) const submitValues = await fillValuesAndSubmit(); // Assert(検証) expect(mockFn).toHaveBeenCalledWith(expect.objectContaining(submitValues)); expect(screen.getByText("登録しました")).toBeInTheDocument(); });
6-2 カバレッジレポートの読み方
カバレッジが高いからといって品質の高いテストであるとは限らない
品質をカバレッジに頼りすぎるのはNGだが、以下をパトロールするには便利な指標になる。
第7章 Webアプリケーション結合テスト
インタラクション*1も関数で抽象化することで、UIコンポーネントのそうだが直感的に理解できる可読性の高いテストコードが書ける。
// https://github.com/frontend-testing-book/nextjs/blob/main/src/components/templates/MyPosts/Posts/Header/index.test.tsx import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import mockRouter from "next-router-mock"; import { Header } from "./"; const user = userEvent.setup(); function setup(url = "/my/posts?page=1") { mockRouter.setCurrentUrl(url); render(<Header />); const combobox = screen.getByRole("combobox", { name: "公開ステータス" }); // インタラクション関数 async function selectOption(label: string) { await user.selectOptions(combobox, label); } return { combobox, selectOption }; } test("公開ステータスを変更すると、status が変わる", async () => { const { selectOption } = setup(); expect(mockRouter).toMatchObject({ query: { page: "1" } }); await selectOption("公開"); expect(mockRouter).toMatchObject({ query: { page: "1", status: "public" }, }); await selectOption("下書き"); expect(mockRouter).toMatchObject({ query: { page: "1", status: "private" }, }); });
第8章 UIコンポーネントエクスプローラー
Storybook
誤解
- コンポーネントに対して1:1のカタログくらいのイメージだったけど違った
実際
- 1コンポーネントでも条件や振る舞いを加味したら複数のStoryが存在する
- 何ができるコンポーネントであるかを知れる
- 細かいコンポーネントの単位で実装/テストが可能になる
- Pageに組み込まなくてもコンポーネント単体で確認ができるのは知らなかった
- AtomicDesignの粒度が人によってマチマチになってる現場とかでは粒度を揃えるきっかけになりそう
- 運用コストが気になるところだがテストで利用できるのでプラマイゼロくらいな印象
- 以下が実装例だが、Givenに当たる部分をStoryで賄える
// https://github.com/frontend-testing-book/nextjs/blob/main/src/components/organisms/AlertDialog/index.test.tsx import { composeStories } from "@storybook/testing-react"; import { render, screen } from "@testing-library/react"; import * as stories from "./index.stories"; const { Default, CustomButtonLabel, ExcludeCancel } = composeStories(stories); describe("AlertDialog", () => { test("Default", () => { render(<Default />); expect(screen.getByRole("alertdialog")).toBeInTheDocument(); }); test("CustomButtonLabel", () => { render(<CustomButtonLabel />); expect(screen.getByRole("button", { name: "OK" })).toBeInTheDocument(); expect( screen.getByRole("button", { name: "キャンセル" }) ).toBeInTheDocument(); }); test("ExcludeCancel", () => { render(<ExcludeCancel />); expect(screen.getByRole("button", { name: "OK" })).toBeInTheDocument(); expect( screen.queryByRole("button", { name: "CANCEL" }) ).not.toBeInTheDocument(); }); });
気になること
- デザイナと共有できるというのはあまり納得いってない
- 開発環境で見れるものという認識だからレビューで見せるくらいしか使えなさそう
- コンポーネントフレームワーク(Vuetifyなど)使ってたら活躍の場が少ない?
第9章 ビジュアルリグレッションテスト
ビジュアルテスト
- Jestでは検証できないブラウザを使った見え方のテスト
- E2Eもブラウザを使ってるが目的が違う
実現方法
著者のオンラインイベント
そこで聞いた耳寄り情報
作業メモ
とりまNuxt3のアプリケーションにStorybookをインストールしておきました。
$ npx sb init --type vue3 --builder vite $ yarn storybook
*1:何らかのユーザ操作をトリガーにシステムがそれに応じた反応を起こすこと