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

コンテキストとセマンティクスを意識してリーダブルなE2Eテストコードを書こう

 コンテキストとセマンティクスを意識してリーダブルなE2Eテストコードを書こう

リーダブルなテストコードについて考えよう ~VeriServe Test Automation Talk No.3~ 2022-07-27 での講演スライドです。

Ea9506ab742ba01d1bea53af6f5649b7?s=128

tsuemura

July 27, 2022
Tweet

More Decks by tsuemura

Other Decks in Technology

Transcript

  1. コンテキストとセマンティクスを意識して リーダブルなE2Eテストコードを書こう Takuya Suemura @ Autify, Inc. リーダブルなテストコードについて考えよう ~VeriServe Test

    Automation Talk No.3~ 2022-07-27
  2. おれは誰だぜ 末村 拓也 開発者、フィールドエンジニア、QAなどを経て、2019年にAutifyに入社。 自動テスト、特にWebのE2Eテストに強い。 現職ではテクニカルサポートを担当。テスト対象とテストフレームワークの互換性の 問題などを調査、解決する役割を担う。 テトリスが好き。

  3. おれが今まで話してきたこと JaSST'22 Tokyo 60分で学ぶE2Eテスト (ベリサーブ 伊藤由貴さんと共演) https://github.com/tsuemura/jasst22-tokyo JTF2020 テストを自動化するのをやめ、自動テストを作ろう https://speakerdeck.com/tsuemura/tesutowozi-dong-hua-surufalsewoyame-zi-

    dong-tesutowozuo-rou 「E2Eテストとはなにか」「理想的なE2Eテストの書き方とは」を ひたすら考えたり話したりし続けている人です
  4. 今日話すこと そもそもE2Eテストってどういうものだっけ? E2Eテストをリーダブルにする理由は? どうやってリーダブルにするの?

  5. そもそもE2Eテストとは

  6. よくあるE2Eテストのイメージ ブラウザとかモバイルデバイスを自動操作して WebアプリとかモバイルアプリのUIを ユーザーが操作するのと同じようにテストする

  7. None
  8. (Webの) E2Eテストで用いられる技術 Webブラウザの自動操作技術 Selenium, Cypress, PlayWright etc. 要素特定の手段 CSS Selector,

    XPath etc.
  9. E2Eテストの例 Cypressによる擬似コード /** ログインする **/ // メールアドレスを入力 cy.type('input[name=email]', 'foo@example.com') //

    パスワードを入力 cy.type('input[name=password]', 'pass1234') // 送信ボタンをクリック cy.click('input[type=submit]') この例では CSSセレクタ で要素を特定し、 それらをクリックしたり、文字を入力したりしている
  10. E2Eテストをリーダブルにする理由は?

  11. E2Eテストをリーダブルにする理由は? =(脳の)メモリの無駄遣いを防ぐ E2Eテストコードは次のようなことを 想像 しながら読まないといけない 長いテストコードを最初から読みながら 今どのページにいるのか想像しながら どのボタンを押しているのか想像しながら 想像をなるべく減らす のがポイント

  12. 読みにくいUI操作の例 // 送信ボタン cy.get('button[type="submit"]').click() // 「OK 」ボタン cy.get('button.primary').click() どちらも CSSセレクタ

    を用いて要素を探索しているが…… type="submit" が送信ボタンであることを知っているのは エンジニアだけ primary クラスがOKボタンに当たってるのは ただの実装上の都合 ユーザーは type="submit" や .primary のような内部的な属性値を使わず、 ラ ベル で探す 読みにくいだけでなく、ユーザー目線でもないので、誰の得にもならない
  13. 読みにくいシナリオの例 // メールアドレスを入力 cy.get('input[name="email"]').type('foobar@example.com') // パスワードを入力 cy.get('input[name="password"]').type('pass1234') // 送信ボタンをクリック cy.get('button[type="submit"]').click()

    このページは ログイン ? それとも 新規登録 ?
  14. 読みにくいシナリオの例 // 新規登録ページにアクセス cy.visit('/register') // メールアドレスを入力 cy.get('input[name="email"]').type('foobar@example.com') // パスワードを入力 cy.get('input[name="password"]').type('pass1234')

    // 送信ボタンをクリック cy.get('button[type="submit"]').click() 直前で「新規登録ページにアクセスした」という 文脈 が無いと読み解けない
  15. ここまでのまとめ 想像で読む部分を減らしたい、そのために ユーザーが要素を探すときと同じ方法で要素を探したい 文脈に依存する書き方を減らしたい

  16. どうやってリーダブルにするの?

  17. どうやってリーダブルにするの? セマンティックな書き方を用いる ユーザーにとって意味のある書き方を用いる コンテキストを明示する 「今何をしているのか」「今どこにいるのか」を明確にする

  18. 読みにくいUI操作の例(おさらい) // 送信ボタン cy.get('button[type="submit"]').click() // 「OK 」ボタン cy.get('button.primary').click() どちらも サイトの内部構造

    を用いて要素を探索しており ユーザー目線 ではない 意味のある = セマンティックな書き方 を使おう
  19. セマンティックな書き方の例 1. 文言を用いる 2. サイトのアクセシビリティを用いる

  20. 1. 文言を用いる Cypress では文言を用いたセレクタを使える 以下の例では Sign Up という文言を含む要素をクリックする cy.contains('Sign Up').click()

    ※ 複数見つかった場合、一番最初に見つかった要素をクリックしてしまうので注意
  21. 文言だけでは出来ないケースはどうしたら? 例: ハンバーガーメニューのアイコン 例: あるラベルを持つ入力フォーム 例: 画像

  22. Testing Library を使ってみよう Testing Library 要素の 役割 や ラベル などを用いてテストコードを書くためのライブラリ

    // 例 getByRole("textbox", {name: / メールアドレス/})
  23. Testing Library を使わない場合 // span タグを用いて作成した擬似的な Submit ボタン <span role="button">Submit</span>

    // button タグを用いて作成した Submit ボタン <button>Submit</button> この2つは button という role と Submit という name を持つ 意味的にはほぼ等価だが テストフレームワークからは異なるセレクタを使わなければいけない cy.get('span').contain('Submit') cy.get('button').contain('Submit')
  24. Testing Library を使う場合 Testing Library を使うとどちらも同じ形で書ける getByRole("button", {name: /Submit/})

  25. アクセシビリティの高いサイトはテスタビリティも高い Testing Library は アクセシビリティ を用いてテストしている つまり、これら3つがシームレスに実現できる 開発者: アクセシビリティ改善 QA:

    アクセシビリティ特性を用いたユーザー目線でのE2Eテスト ユーザー: アクセシビリティの利用 テスターにとってのアクセシビリティの優先度は実は高い テストしにくいサイトがあったときに、 「テストしやすく」ではなく「アクセスしやすく」という提案が出来るかも
  26. どうやって使うの? Testing Library は Cypress, Puppeteer, TestCafe, PlayWrightなど主要なテストフレ ームワークに対応 簡単に試したいならChrome拡張

    Testing Playground を使おう
  27. コンテキストを明示する

  28. コンテキストを明示する 読みにくいシナリオの例(おさらい) // メールアドレスを入力 cy.get('input[name="email"]').type('foobar@example.com') // パスワードを入力 cy.get('input[name="password"]').type('pass1234') // 送信ボタンをクリック

    cy.get('button[type="submit"]').click() このページは ログイン ? それとも 新規登録 ?
  29. コンテキストを明示する手法 1. Page Object 2. Context Enclosure

  30. Page Object Pattern の利用 ページ内のロケーター、ページ特有の操作などをオブジェクトにまとめるテクニック 本来はメンテナンス性向上のための技だが、副次的にコンテキストを明示することも 出来る const loginPage =

    new LoginPage() loginPage.getEmailInput().type('foo@example.com') loginPage.getPasswordInput().type('pass1234') loginPage.getSubmitButton().click() どのUI要素も loginPage という Page Object のインスタンスから生えている = ログインページ内の要素であることが明示的に示されている
  31. Page Object の実装例 class LoginPage { getEmailInput() { return cy.get('input[name="email"]')

    } getPasswordInput() { return cy.get('input[name="password"]') } getSubmitButton() { return cy.get('button[type="submit"]') } } ログインページ内の要素をあらかじめ PageObject 内に定義する
  32. Page Object は結構手間がかかる 例えば、ログインページに Remember me? という チェックボックスを追加したが、Page Objectには追加していないとする const

    loginPage = new LoginPage() loginPage.getEmailInput().type('foo@example.com') loginPage.getPasswordInput().type('pass1234') cy.contains('Remember me?').check() // ここだけ loginPage に属してないように見える loginPage.getSubmitButton().click() 要素を追加した際、かならず Page Object に要素を登録する必要がある Page Object はコンテキストを明示する目的に対しては 重い アプローチ
  33. Context Enclosure 現在のコンテキストに応じてスクリプトの一部を囲う Page Objectよりも「コンテキストを明示する」という意図が明確になる cy.visit("https://demo.realworld.io/#/register"); // 新規登録ページに遷移 // この部分が

    Context Enclosure cy.onRegisterPage(cy => { cy.findByPlaceholderText("Username").type("foobar") cy.findByPlaceholderText("Email").type("foobar@example.com") cy.findByPlaceholderText("Password").type("Pass1234") } ※ 名前は先日考えたのでググってもろくなのがでてきません 実装のサンプルは https://zenn.dev/tsuemura/articles/13b0ea44c1a20a
  34. Context Enclosure の実装方法 Cypress.Commands.add("onRegisterPage", (fn) => { fn(cy); }); 1ページにつき3行で実装でき軽量

    Cypressの場合はカスタムコマンドで実装する
  35. コンテキスト内でのみ利用できるコマンド Cypress.Commands.add("onRegisterPage", (fn) => { Cypress.Command.Add("showMessage", (message) => { //

    独自コマンドの定義 cy.log(message) }) cy.url().should('include', 'register') // register ページにいることを確認 fn(cy); }); onRegisterPage の中でだけ利用できる showMessage というコマンドを定義し た 例えば login や fillCredentials のようなhelperを定義してあげるとテストコ ード記述が楽になる 同時に onRegisterPage が呼ばれた段階で register を含むURLにいることを確 認している
  36. Context Enclosure の利点 最低限の実装であれば各ページ3行ぐらいで済むので楽 全てのページに実装するのもそう大変ではない 「あるコンテキストにいる」という検証をセットで実装できる コンテキストに応じて独自のコマンドを実装できる Context Enclosure の欠点

    こないだ考えたばっかりなのであんまり枯れたアイディアではないこと
  37. 今日ほとんど Context Enclosure の話をしに来たんで フィードバックもらえると助かります

  38. まとめ

  39. まとめ 悩まずにテストコードを読み書きするためにリーダビリティに気を使う そのためにセマンティクス(≒ アクセシビリティ)とコンテキストの2つを紹介した ユーザー目線で、文脈が明確なテストコードを書こう

  40. Enjoy Testing! スライドか?欲しけりゃくれてやるぜ…… 探してみろ 今日の発表資料の全てをSpeakerdeckに置いてきた (Twitter / Zoomのチャットとかでも共有されると思います)