Slide 1

Slide 1 text

webフロントエンドテスト ⾃動化 フロントエンドエキスパートチーム 左治⽊隆成

Slide 2

Slide 2 text

コンセプト 誰に • webフロントエンド開発に携わる⼈たちに なんと⾔ってほしい • 「フロントエンドテストの書き⽅にイメージがついた」 • 「最近のwebフロントエンドのテストの潮流をつかめた」 • 「フロントエンドのテスト書いてくぞー!!!!」

Slide 3

Slide 3 text

⽬次 1. テストコードについておさらい • テストコードの意義などを軽くおさらい 2. フロントエンドテストの⽬的と⼿法 • どういった種類のテストがあるのか • 何を保証したくてフロントエンドテストを書くのか 3. フロントエンドテストを⽀える技術 • どのようにフロントエンドテストを書いていけば良いのか • 具体的なライブラリの使い⽅を知る 4. テストを書いてみよう! • 実際にインテグレーションテストを書いてみる

Slide 4

Slide 4 text

テストコードについて 何のためにテストを書くのか? • サービスの信頼のため • コードに⾃信を持つため • 綺麗なコードを維持するため • コミュニケーションのため • リファクタをしやすくするため • …etc

Slide 5

Slide 5 text

テストコードを書こう!

Slide 6

Slide 6 text

開発スピードが遅くなる? → そんなことはない!

Slide 7

Slide 7 text

品質とスピードはトレードオフではない • 「内部品質を犠牲にしているから遅い」 • “内部品質への投資の損益分岐点は 3年後とかではなく 1ヶ⽉以内に現 れる” • 参考) 3⽉に来ていただいた t-wadaさんの社内公演を聞くべし • (社内向けリンク)

Slide 8

Slide 8 text

テストコードの意義 • 数ヶ⽉先に⾃分が書いたコードが残っているはずならちゃんとテスト を書こう • 実際kintoneのClosureのコードは10年⽣きてます。 • そうでなくても数ヶ⽉前の⾃分は他⼈です • 実装している時でも、「テストコード書いたおかげでレビュー前に実 装が不⾜してる箇所を気づけた」といったことがある

Slide 9

Slide 9 text

テストコードを書こう!

Slide 10

Slide 10 text

でもどう書けばいいのさ

Slide 11

Slide 11 text

フロントエンドテストの ⽬的と⼿法

Slide 12

Slide 12 text

テスト範囲による分類 ⼀般的に4つの範囲に分けることが多い • 静的解析 • 単体テスト • 結合テスト • E2Eテスト

Slide 13

Slide 13 text

静的解析 • lintツール、型解析ツールと呼ばれるもの • モジュール間のインターフェース不整合やコードの表現の⼀貫性を保 証する

Slide 14

Slide 14 text

単体テスト • ⼀つのモジュールの機能を保証する • 複雑なモジュールのエッジケースなどを検証するのに有効 • ユニット(Unit)テストとも

Slide 15

Slide 15 text

結合テスト • 複数のモジュールを組み合わせた挙動・機能をテストする • ある程度広範囲なテストをカバーできる • インテグレーション(integration)テストとも

Slide 16

Slide 16 text

E2Eテスト • 本番相当の環境で、システム・アプリケーションが⼀貫して動作する ことを確認するテスト • フロントエンド〜バックエンドを⼀貫してテストする • よりユーザーの体験に近い形でテストができる

Slide 17

Slide 17 text

それぞれの特徴 静的解析 単体テスト 結合テスト E2Eテスト 実⾏コスト テスト範囲 不安定さ 低 ⾼ 広 狭 不安定 安定

Slide 18

Slide 18 text

テストピラミッドモデルへ 静的解析→単体テスト→結合テスト→E2Eテストの順に • 👎 実⾏保守コストや開発・実⾏速度は⾼くなる • 👍 広範囲のテストが可能になっていく → テストの⽐率をピラミッド型にすればコストと保証する品質のバラ ンスが取れるのでは?

Slide 19

Slide 19 text

テストピラミッド E2E 結合テスト 単体テスト $ $ $ ¢ 🏎 🐢

Slide 20

Slide 20 text

…ここまでが復習

Slide 21

Slide 21 text

フロントエンドにフォーカス するとどうなるか?

Slide 22

Slide 22 text

前提1 : 近年のフロントエンドの進化 • サーバーサイドとフロントエンドが別れた世界に • フロントエンドできること・やることが増える • 複雑な状態管理 • SPA によるシームレスなUI更新 • ServiceWorkerによるキャッシュ管理やオフライン対応 • etc… → フロントエンド単体で複雑性が増している。

Slide 23

Slide 23 text

前提2 : フロントエンド特有なこと • ユーザーからのインタラクションを起点に動作する • ある程度単純なコンポーネントの挙動の組み合わせで複雑になりがち • ⽂書構造があり、それをブラウザや読み上げソフトが解釈する • ⾒た⽬・スタイルがある

Slide 24

Slide 24 text

フロントエンドにフォーカスすると⾒えてきたもの • フロントエンドの進化 • フロントエンドに閉じてても複雑な挙動が増える • フロントエンド特有の事情 • ⼀つのコンポーネントだけで成⽴する機能は少ない • ユーザーからのインタラクションを起点に動作する • ⾒た⽬・⽂書構造がある

Slide 25

Slide 25 text

フロントエンドに特化するとどうなるか • Testing trophyモデルの提唱 • フロントエンド特有の事情を鑑みた新しいテストモデルの提唱 • VRT・a11yテストの普及 • ⾒た⽬・⽂書構造があるをテストする⼿段

Slide 26

Slide 26 text

Testing trophyモデル • kent.c.Dodds⽒が提唱するモデル • The Testing Trophy and Testing Classifications • コード・ロジックのカバレッジではなく、 ユーザーからみたユースケースのカバレッ ジを重視すべきという考えに基づく

Slide 27

Slide 27 text

Testing trophyモデル • スピード・コストに加えて信頼度とのバランスをとる • 信頼度 = どれだけ動作に⾃信を得られるか。 • “The more your tests resemble the way your software is used, the more confidence they can give you.” • ユーザーの操作を起点とした、複数のモジュール・コンポーネントを 跨ぐテストをコストを抑えつつ書いていきたい • 結合テストを充実させていくと良いのではないか • Write tests. Not too many. Mostly integration.

Slide 28

Slide 28 text

Testing trophy : 静的解析 • LintやTypeScriptが該当する • ⼀般的にカバレッジには含まれない • 適切なルールの運⽤と型を厳格にすること でロジックのミスをかなり減らすことがで きる

Slide 29

Slide 29 text

Testing trophy : 単体テスト • オブジェクト・関数、hooks、単体コン ポーネントのテストなどが該当する • エッジケースの多いロジックなど⼀つのモ ジュール単位で複雑な挙動をもつものには 最適

Slide 30

Slide 30 text

Testing trophy : 結合テスト • コンポーネントや関数など複数のものが合 わさったもののテスト • ここを厚くすることでユースケースのカバ レッジをあげる • 使われているコンポーネントや機能の結合 テストが通れば、動きが保証できる

Slide 31

Slide 31 text

Testing trophy : E2Eテスト • 信頼度が⼀番⾼く、コストもかかるもの • 重要なユーザー体験や最低限の動作を保証 するなどの利⽤することが多い

Slide 32

Slide 32 text

結合テストのコスト Q . 結合テストの実⾏コスト⾼くないの? A. 後述する「DOMをエミュレートする」タイプのテストツールだと以 下のような理由からE2Eテストより実⾏コストを低く抑えられる • ヘッドレスブラウザを介していない • 通信やIOを基本的にモックしている

Slide 33

Slide 33 text

VRTとa11yテスト

Slide 34

Slide 34 text

VRT : Visual Regression Test = ⾒た⽬の回帰テスト つまり「⾒た⽬変わっちゃってない?」を毎度確認するテスト

Slide 35

Slide 35 text

a11yテスト • Lintツールである程度チェック可能 • eslint-plugin-jsx-a11y など • コンポーネントの結合テストなどでもアクシブルな情報を利⽤した操 作などでテストが可能(後述) • 読み上げなど、⾃動化が難しいものは別途⼿動試験をしたりできると 良い

Slide 36

Slide 36 text

フロントエンドテスト を⽀える技術

Slide 37

Slide 37 text

各テストよく使われるライブラリ • 静的解析 • eslint : JavaScriptのリントツールとしてデファクトなライブラリ • Typescript : 型チェックを⾏える • 単体テスト • Jest : メジャーなJavaScriptテストフレームワーク • Vitest : Jest互換の⽐較的新しいJavaScriptテストフレームワーク

Slide 38

Slide 38 text

各テストよく使われるライブラリ • 結合テスト • Testing Library : Domをエミュレートして動作を実⾏確認できるツール • jest-dom : jestのDOM⽤拡張。DOM⽤のマッチャーなどが追加される。 • E2Eテスト • Cypress : Selenium以降に台頭したE2Eテストフレームワーク • PlayWright : Microsoftが中⼼に開発する後発のOSS E2Eテストツール。

Slide 39

Slide 39 text

各テストよく使われる技術 VRTテスト • Chromatic (Storybook) • Storybook公式が提供するサービス。 • Storybookで表⽰するコンポーネントでそのままVRTが可能。 • reg-suite × E2Eテストツールによるスクリーンショット • E2Eテストツール で撮ったスクリーンショットを元にVRTを実⾏する

Slide 40

Slide 40 text

実際にどんな感じ書くの?

Slide 41

Slide 41 text

実践編 Testing LibraryとJestを使った 結合テストの書き⽅ Write tests. Not too many. Mostly integration.

Slide 42

Slide 42 text

結合テストの3要素 やりたいことは基本的に3つ 1. 要素を探して取ってきて 2. 何らかの操作をして 3. 結果を検証する

Slide 43

Slide 43 text

結合テストの3要素 それぞれの分担 • 要素を探して取ってきて : TestingLibrary • 何らかの操作をして : TestingLibrary • 結果を検証する : Jest(の拡張) / TestingLibrary

Slide 44

Slide 44 text

要素を探す

Slide 45

Slide 45 text

要素の探し⽅ 以下のような感じで要素を取得する • TestingLibraryには⾊々な要素の探し⽅(=クエリ)がある。 • 適切なクエリを使って要素を取得することが⼤事 const addButton = await screen.findByRole('button’)}

Slide 46

Slide 46 text

TestingLibraryのクエリ分類 get find query All (なし) ByRole ByLabelText ByPlaceholderText ByText ByDisplayValue ByAltText ByTitle ByTestId

Slide 47

Slide 47 text

TestingLibraryのクエリ分類1 get〇〇 / query〇〇/ find〇〇 の違いと Allのあるなし ⾒つからない時 1つ⾒つかった場 合 2つ以上⾒つかっ た場合 ⾒つからない場合 にretry getBy○○ error 要素 error しない queryBy○○ nullを返す 要素 error しない findBy○○ error 要素 error する getAllBy○○ error 要素⼀つの配列 複数要素の配列 しない queryAllBy○○ 空配列を返す 要素⼀つの配列 複数要素の配列 しない findAllBy○○ error 要素⼀つの配列 複数要素の配列 する

Slide 48

Slide 48 text

TestingLibraryのクエリ分類2 どのような特徴で要素を探すか(By〇〇の部分) • 使うクエリには優先度がある • 適切なクエリを使って要素を取得することが⼤事(再掲) 原則 : ユーザーの操作に沿うこと ..「ユーザーの操作に沿う」ってなに?

Slide 49

Slide 49 text

よくありがちだけど🫤な取得⽅法 body → div[0] → ul → li[0] → … → button • みんな普段ページを操作するとき、DOM treeみて要素探してるの? getById("password-input") • みんなDOMのIdとかClassName⾒てページ操作してるの?

Slide 50

Slide 50 text

そんなわけない!

Slide 51

Slide 51 text

普段の操作を思い出してみる ユーザー名とパスワードを⼊⼒してログインしてください

Slide 52

Slide 52 text

普段の操作を思い出してみる どうして上のテキストボックスがログイン名だと分かった? • → プレイスホルダーにログイン名って書いてあったから どうして右下がログインボタンだと分かった? • → ログインと書いてあるボタンだから

Slide 53

Slide 53 text

優先されるべきクエリ 以下のような情報から要素を取得すべき • アクセシビリティロール • ラベル • placeholder • テキスト これらは視覚的な認知とスクリーンリーダーなどによる機械的な解釈が ⼀致している(はず)から

Slide 54

Slide 54 text

視覚的認知できるのにうまく取得できない… = ⾒た⽬と⽂章の構造・解釈が⼀致していない = そもそもa11y上の問題を抱えている可能性が⾼い 可能な限り治しましょう! 誰でもアクセスできる情報をもとにしたクエリから要素を取得すること はa11yを確認することにもつながる!

Slide 55

Slide 55 text

具体的なTestingLibraryのクエリ 全ての⼈にとってアクセシブルなクエリ(なるべくこれを使おう) • ByRole : WAI-ARIAのrole属性から取得する • ByLabelText : フォームなどのラベルから取得する • ByPlaceholderText : ⽂字⼊⼒のプレイスホルダーから取得する • ByText : ⼊⼒とかではない⽂書コンテンツなどを取得する • ByDisplayValue : フォームないの現在値から取得する。

Slide 56

Slide 56 text

具体的なTestingLibraryのクエリ HTML5 および ARIA 準拠ではあるものの挙動・解釈はブラウザ・⽀援 技術によって⼤きく異なるクエリ • ByAltText : 画像などををalt text から取得する • ByTitle : title属性から要素を取得する。

Slide 57

Slide 57 text

具体的なTestingLibraryのクエリ どうしてもうまく要素が取れない時・要素が動的に⽣成される時などに 利⽤するクエリ • ByTestId : 要素につけた data-testid 属性か要素を取得する

Slide 58

Slide 58 text

結合テストの3要素 やりたいことは基本的に3つ 1. 要素を探して取ってきて 2. 何らかの操作をして 3. 結果を検証する

Slide 59

Slide 59 text

要素に対する操作 userEventオブジェクトを利⽤する クリック • userEvent.click([クリックしたい要素]) テキスト⼊⼒ • userEvent.type([⼊⼒したい要素],”打ちたい⽂字列”)

Slide 60

Slide 60 text

結合テストの3要素 やりたいことは基本的に3つ 1. 要素を探して取ってきて 2. 何らかの操作をして 3. 結果を検証する

Slide 61

Slide 61 text

よくある検証したいこと 特定の要素があるか?or 消えているか? 要素が特定の数あるか? 正しくリクエストが⾶んでいるか?

Slide 62

Slide 62 text

特定の要素があるか?or 消えているか? testing-library とjestを使う // 要素があるか? // → 要素を findBy や getBy で取得する(⾒つからないとErrorになるので) const addButton = await screen.findByRole('button', { name: '追加' }); // 要素が消えているか? // → 要素を queryByで取得し、null であることを確かめる const addButton = screen.queryByRole('button', { name: '追加' }); expect(addButton).toBeNull();

Slide 63

Slide 63 text

要素が特定の数あるか? testing-library とjestを使う // 要素が特定の数あるか? // → 要素を findAllBy などで探し const nameList = await screen.findAllByRole('listItem’,); // 個数を確かめる expect(nameList.length).toBe(3);

Slide 64

Slide 64 text

正しくリクエストが⾶んでいるか? ..の前にリクエストのモック(テストダブル)が必要

Slide 65

Slide 65 text

リクエストのモックについて 結合テストがカバーできるのはフロントエンドの範囲内 • = 通信はモックする(ダミーを作る)必要がある ServerSide FrontEnd request ?

Slide 66

Slide 66 text

通信のモック 最近では msw (mock service worker) というライブラリを使うことが 多い • service worker上で通信を横取りする • リクエストに対応したレスポンスを設定しておくことで通信をモック Jestと併⽤するとリクエストの中⾝・呼ばれた回数・タイミングなどを 検査できる

Slide 67

Slide 67 text

通信のモック イメージ ServerSide FrontEnd request MSW response(mock) requestを⾒る & いい感じの mockを返す

Slide 68

Slide 68 text

改 : 正しくリクエストが⾶んでいるか? リクエストのモックとjestのモック関数を使う 1. jest.fn() を利⽤してモック関数を作る • モック関数 = どんな引数で呼ばれたか・何回呼ばれたかなどを検証可能 2. リクエストをモックする(※⽅法は後述) 3. 2.でリクエストが来たらモック関数にリクエストの中⾝を渡す 4. モック関数がどう呼び出されたか検証する

Slide 69

Slide 69 text

TestingLibraryを⽤いた結合テストだと難しいもの 1. ホバー時のスタイル確認 • DOMをエミュレートしてるだけなのでCSSの機能を確認するのは難しい 2. ドラッグ&ドロップのような操作 • ドラッグの位置情報などを計算することは基本できない 3. スクロールの絡む操作 • そもそも画⾯のサイズによって変わるのものなので難しい

Slide 70

Slide 70 text

⻑々と話してきましたが…

Slide 71

Slide 71 text

講義だけだど眠くなっちゃう ので実際に書いてみよう!

Slide 72

Slide 72 text

演習

Slide 73

Slide 73 text

演習で使う環境について軽く説明 今回はStorybookを利⽤ • Storybookでコンポーネントのインタラクションテストが書ける • = Storybook上でJestやtesting-libraryが動いてるイメージ • メリット • ⽬で実⾏結果が確認できる • 環境構築が楽

Slide 74

Slide 74 text

演習をする前に 必要なもの • git 環境 • Node.js v18 以上の環境 • (社内リンク) • お気に⼊りのエディタ • 宗教上の理由がなければ vscode が楽です。

Slide 75

Slide 75 text

演習準備 1. 以下のリポジトリをclone • https://github.com/sajikix/frontend-test-training-2023 2. README.mdを開く 3. READMEに従って storybookを起動 1. npm ci と npm run storybook を叩くだけ 4. うまく⾏くと https://localhost:6006 でstorybookが起動する。

Slide 76

Slide 76 text

Storybookの操作説明 画⾯共有して説明します。

Slide 77

Slide 77 text

コードの軽い説明 • src/page にテスト対象のコンポーネントがいる • 〇〇.stories.tsx ファイルがStorybookを表⽰するためのファイル • 〇〇.stories.tsx ファイルを開くと、Task1 みたいな名前のオブジェク トがある • この⼀つ⼀つがStorybookObjectと呼ばれるものでStorybook上の1 ページに対応してる

Slide 78

Slide 78 text

StorybookObjectの説明 • play関数の中に処理を書いていく • Storybook上で書かない場合と⼤まかには⼀緒! • セットアップとかcanvas取ってくるとこが微妙に違うくらい • 変更して保存すると⾃動的にStorybookがリロードされる

Slide 79

Slide 79 text

問題を解いてみよう! • 問題1~5を⽳埋めで書いてみよう • 書けたと思ったらStorybookのinteractionタブを確認! • うまく動いていればPASSEDになるよ! • 解答例は answers/ にあるので解けたら⾒てみよう • 質問あったら遠慮なくコメントください !

Slide 80

Slide 80 text

盛り込めなかった話 E2Eテストでの⾃動化⼿法について VRTテストの詳しい実装⽅法 単体テストとの細かい境界 キーボード操作など少し複雑な操作について

Slide 81

Slide 81 text

参考⽂献・学びたい⼈へ 参考⽂献 • https://kentcdodds.com/blog • https://testing-library.com/ • https://jestjs.io/ より詳しく学びたい⼈へ • 『フロントエンド開発のためのテスト⼊⾨ 今からでも知っておきたい ⾃動テスト戦略の必須知識』吉井 健⽂ (著) がおすすめです。