末村氏の自己紹介とセッションの概要

末村拓也氏:「コンテキストとセマンティクスを意識してリーダブルなE2Eテストを書こう」。書こうね! 以上! というわけにもいかないので、話していきたいと思います。

伊藤さん(株式会社ソニックガーデン 伊藤淳一氏)みたいに本を出してないのであまり話すことはありませんが、今、オーティファイという会社に勤めててE2Eテストのノーコードでやるようなものを作っています。みなさん興味があったら「オーティファイ」でググると何か出てくると思います。前座で話しましたが、テトリスが大好きです。

僕が誰かということよりも、どちらかというと、どういうことをしゃべってきた人なのかを、先にみなさんに説明しておきたいです。僕はずっとE2Eテストについて「いつ・なに」とか「理想的なE2Eテストの書き方とは」みたいなことを、ひたすら考えて話し続けている人です。

そういうものをがんばって追求しているので、E2Eテスト固有のリーダビリティ、あるいはアンリーダビリティみたいなものがなんとなくわかってきました。今日はそのあたりの話をしていきたいと思っています。よろしくお願いします。

今日話したいことは、「そもそもE2Eテストってどういうものだっけ?」ということと、もしかしたらその前提がわからない人がいるかもしれないので、(前提に)軽く触れておきたいと思います。

それから、E2Eテストをリーダブルにする理由。E2Eテスト固有の話をしていきたいと思います。

最後に「どうやってリーダブルにするの?」という具体的な実装のところ。時間がないのでしっかり触れられるかどうかわかりませんが、やっていきたいと思います。

そもそもE2Eテストとは何か?

最初は「そもそもE2Eテストってなんだっけ?」の簡単なまとめをしておきましょう。基本的にE2Eテストと呼ばれるものは、ブラウザやモバイルデバイスを自動操作して、WebアプリとかモバイルアプリのUIをユーザーが操作するのと同じようにテストするもの、と捉えてもらえればと思います。

(デモ動画 開始)

(スライドを示して)これはCypressというツールの例です。ここで自社のものを出さないのが僕の悪い癖ですが、Sign upフォームにユーザー名、Eメール、パスワードを入力していっているのがわかりますね。

(デモ動画 終了)

ブラウザを実際に動かしていくので、E2Eテストでは当然Webブラウザの自動操作の技術が使われたり、あるいは要素特定のためにCSS Selectorと呼ばれるものや、XPathと呼ばれるものを使ったりします。要素特定の手段として使われるものが、この先鍵になるので、CSS Selectorという用語を覚えておいてください。

E2Eテストのコードはどんなもの書くのかを軽く紹介したいと思います。(スライドを示して)こちらにCypressというE2Eテストのフレームワークによる疑似的なコードが書いてあります。この例だと、ログインするというのが、この全体の説明で、その後のコメントはそれぞれの行でやっていることですね。メールアドレスを入力する、パスワードを入力する、送信ボタンをclickする。

この例だと、input[name=email]というCSS Selectorを使って要素を特定して、clickしたり文字入力したりしています。例えばメールアドレスだと、foo@example.comと入力しています。これがE2Eテストの簡単な説明でした。

なぜE2Eテストをリーダブルにするのか

次は、E2Eテストをリーダブルにする理由。脳のメモリの無駄遣いを防ぐためです。E2Eテストのコードは、(スライドの)以下のようなことを想像しながら読まないといけないと思っています。

E2Eテストは基本的にコードが長いです。これはインターフェースがユーザーインターフェースなので、主にユーザーストーリーと言われるようなユースケースをテストコードにしているため、すごく長くなりがちです。そうすると、いろいろなページにまたがりながら読むことになるので、今どのページにいるのかを想像しながら読む必要があります。

さらに、どのボタンを押しているのかも、先ほどのCSS Selectorを見て想像しながら読まないといけない。この想像をなるべく減らすことがポイントになってくるんじゃないかと思っています。

2つの読みにくいUI操作の例

読みにくいUI操作の例を見てみましょう。(スライドを示して)これもまたCypressのテストコードとして書いています。この例だと、送信ボタン、button[type="submit",]というCSS Selectorを探してきて、その様子をclickしています。

「OK」ボタンというコメントがついていますが、これはWebのhtmlあるいはCSSに詳しい人、多少触った人はわかると思いますが、buttonというタグでprimaryというclassがついているものを「OK」ボタンとみなしてclickしています。

どちらもCSS Selectorを用いて要素を探索していますが、[type="submit"]は送信ボタンであることが多いです。多いというか、基本的に送信ボタンです。

でも、これを知っているのは基本的にエンジニアや中の人だけです。さらにいうと、ボタンにprimary classがあたってて、それが「OK」ボタンだというのは実装上の都合だけです。ユーザーはどうやって送信ボタンや「OK」ボタンを探すかというと、[type="submit"]やprimaryは絶対使わないんです。

普通はUIで探すので、内部的な属性値は使いません。ラベルで探しますよね。「送信」って書いてあるボタンとか、「OK」って書いてあるボタンとか。つまり、こういう記述は読みにくいだけでなくユーザー目線でもないので、誰の得にもならないんです。

(スライドを示して)もう1つ読みにくいシナリオの例を見せましょう。この例では、メールアドレスを入力して、パスワードを入力して、送信ボタンをclickしています。こういうコードだけを見て、このページがログインなのか新規登録なのかを判断できますか?

答えは、できません。直前に「新規登録ページにアクセス」みたいなコードあるいはコメントがないと、絶対に判断できません。つまり、文脈がないと読み解けないものなのです。

ここまで早口で説明しましたが、今日話したいのは「想像で読む部分を減らしたい」ということです。そのために、ユーザーが要素を探す時と同じ方法で要素を探したい。そうすれば、UIを想像しながら読む手間がなくなります。

さらに、文脈に依存する書き方を減らしたい。先ほどの、文脈をきちんと覚えながら読んでいかないと、ログインページなのか新規登録ページなのか、似たようなページが2つあったらわからないということです。

E2Eテストをどうやってリーダブルにしていくか?

というわけで、ここからは今話したことを解決して、リーダブルにしていく具体的な方法を紹介しようと思います。どうやってリーダブルにするのか。ここでタイトル回収です。セマンティックな書き方を用いて、コンテキストを明示したいと思います。

セマンティックは意味論的みたいな読み方になると思いますが、ここではユーザーにとって意味のある書き方を用いると捉えてください。つまり、内部実装に依存するような書き方ではなく、ユーザーに見えている値や、ユーザーが受け取ることができ得る値を使いましょうということです。

それからコンテキストを明示する。これは、先ほどコメントで「今何をしているのか」「今どこにいるのか」と書いていたものを、もっとコードベースで明確にしていきましょう、ということです。

セマンティックな書き方を用いる

まず、セマンティックな書き方のほうで、読みにくいUI操作の例をおさらいしておきましょう。(スライドを示して)ここでの送信ボタンと「OK」ボタンのセレクタは、どちらもサイトの内部構造を使っています。

こうやって要素を探索するとユーザー目線ではありません。ちょっと脇道にそれますが、こういう書き方は昔から批判のもとになっていました。しかし、批判の対象になりつつも、よく使われていました。

ただ、最近のモダンな考え方では、こういう書き方はあまり良くないということになっています。なのでこれから、意味のあるセマンティックな書き方を使おうという話をしていきます。

セマンティックな書き方とは具体的にどういうものなのだろうということで、文言を用いることと、サイトのアクセシビリティを用いることの2つのやり方を紹介しようと思います。

セマンティックな書き方の例 文言を用いる

最初は「文言を用いる」です。先ほどから使っているCypressというテストフレームワークでは、文言を用いたセレクタを使います。例えば、cy.contains('Sign Up,').click()。

これはSign Upという文字が含まれている要素ならなんでも取ってきてしまうので、もしかしたらSign Upというリンクがナビゲーションバーにあって、かつSign Upというボタンもある場合は、ボタンのほうではなくナビゲーションバーをclickしてしまうかもしれない。

これだけで使うのは大変な面もありますが、少なくともCSS Selectorを使うよりは100倍わかりやすい。ただ、やはり曖昧かもしれないところはあります。

文言でclickしたり、要素探索したりできるケースもあります。しかし、表示されている文字だけでやろうとすると、例えばアイコンとかあるラベルを持つ入力フォームを口で言ってもわかりにくいと思うので、この後説明します。

ラベルと入力フォームのタグが分かれているケースや画像は、ゴリラの画像が出ていて「ゴリラ」とセレクタを書いたら見つけてくれるとうれしいですが、そういうわけにはいかない。こういうのは、やはり文言だけだと難しいです。

テストコードを書くためのライブラリ、Testing Library

Testing Libraryというライブラリがあります。そのままの名前なので、Testing Libraryでググるとすぐ出てきます。ググってみてください。これは、要素の役割やラベルなどを用いてテストコードを書くためのライブラリです。

(スライドを示して)例として挙げているgetByRoleというものですが、textboxの役割を持ち、かつメールアドレスという名前を持っている要素を見つけるものとなっています。

Testing Libraryを使わない場合と使う場合の話をしてみましょう。Testing Libraryを使うメリットの話です。

(スライドを示して)ここに2つのボタンがあります。spanタグを用いて作成した疑似的なSubmitボタン。それから、buttonタグを用いて作成したSubmitボタン。タグだけだとわかりづらいかもしれません。

CSSは(みなさん)知っていると思いますが、htmlにはCSSというStyle Sheetを用いて、どんなデザインでもあてることができます。

なので、どんなタグにもボタンというタグでなく、ボタンっぽい要素を作ることができます。ただし、それだと「この要素がボタンである」ということが明確にわからないので、roleとしてボタンを持たせたりすることがあります。そうすると、この2つはボタンというroleと、Submitというnameを持つ2つのボタンになります。

意味的にはほぼ等価で同じものだと思いますが、テストフレームワーク側からだと、違うセレクタを使う必要があります。('span").contain('Submit')、('button').(contain('Submit')。そうすると、もともとspanタグを用いて実装してたものをbuttonに実装し直したら、テストコードを直さないといけないんです。

リーダビリティから離れたメンテナンシビリティの話をしていますが、違うものを使わないと、後でテストコード変更する必要が出てくる可能性があります。

Testing Libraryを使うと、基本的にどちらも同じかたちで書けます。すみません。今回あまり準備の時間が取れなくて、どちらも同じかたちで書けると言い切りましたが、本当にそうだったかなと若干自信がなくなってきました。そういう思想のライブラリだということを強調して帰りたいと思います。

アクセシビリティが高いサイトは、テスタビリティも高い

先ほどTesting Libraryは、アクセシビリティを使うものだと話しました。ここで言っておきたいのは、アクセシビリティが高いサイトは、テスタビリティも高いんです。というのも、Testing Libraryが先ほどやっていた、同じ役割の要素ならタグの名前が違っててもテストできるということは、アクセシビリティツリーを使っているからなのです。

つまり、例えば開発者にとってはアクセシビリティの改善を進めることができます。QAにとっては、アクセシビリティ特性を用いたユーザー目線のE2Eテストができます。ユーザーにとっては、アクセシビリティの利用ができます。

ということで、テストしにくいサイトを改善したら、アクセシビリティの改善にもなっちゃったみたいな世界観ができるんです。

テストしにくいサイトがあった時、テストしやすくするだけではなく、アクセスしやすくするという、よりユーザー目線に立った改善が提案できるかもしれない。かつ、恐らく読みやすさも高いということになります。

Testing Libraryは、先ほどからよく出ているCypressを含めて、いろいろなフレームワークと一緒に使えるので使ってみてください。Chrome拡張でTesting Playgroundというカエルのアイコンがあったりしますが、こういうものを使ってもらってもいいと思います。

セマンティックな書き方の例 コンテキストを明示する

最後に、「コンテキストを明示する」をやっていきたいと思います。コンテキストとは何かを、まずおさらいしてみましょう。読みにくいテスト、このページはログインページなのか新規登録なのかわからないという問題がありました。

コンテキストを明示する方法、もちろん何かコメントをつけたりしてもいいですが、できればコードを使って明示していきたいと思います。ここで、Page ObjectとContext Enclosureという2つの手法を紹介したいと思います。

Page Objectは、Page Object Patternという名前でもよく知られています。例えば、LoginPageというObjectを先に定義しておいて、その中にgetEmailInput()やgetPasswordInput()というかたちでLoginPageに属するページの要素、この場合だと、Emailのインプットフォームを見つけてくるような命令を事前に定義しておく。

そうすると、どのUI要素もLoginPageから入れるように見えるので、「このコードはLoginPageを操作してるんだな」ということが、わりと明確に見えてくるんです。

実装の例はサクッと飛ばしましょう。興味のある方は後でスライドを見てください。LoginPageの中の要素をあらかじめPage Objectに定義しておく必要があることだけ覚えておいてもらえればOKです。

今紹介しましたが、ちょっと手間がかかるやり方だということを話しておきたいと思います。Page Objectそのものが悪いということではなく、コンテキストを明示するという目的に対して、ちょっと重いという話をしたいんです。

例えば、ログインページに「Remember me?」というチェックボックスがあることがよくあると思います。もともとなかったんだけれど追加したり、Page Objectに追加するの忘れていてテストコード書き始めて、とりあえずアドホックのテストコードのほうに追加しようと思った時に、ここだけログインページ起点ではなくなってしまうんです。

これは、LoginPageというPage Objectに「Remember me?」というチェックボックスを追加していなかったからです。要素を追加したり変更したりする時は、必ずPage Objectに要素を登録する必要があるので、コンテキストを明示するという目的に対しては、ちょっと重いアプローチなんじゃないかなと思っています。

テストコードを囲うものを作りその中でテストコードを書く、Context Enclosure

ここからはContext Enclosureというやり方を紹介したいと思います。これは、例えばcy.onRegisterPageというテストコードを囲うものを作っておいて、その中で普通のテストコードを書きましょうというアプローチです。

そうすると、この中では「ヤベエ、ここでログインから新規登録に変わっちゃった。新規登録ページになります」の中で操作をしていることを明示するという目的で、onRegisterPageを作って、その中で普通にテストコードを書いています。

これはこの間思いついたアイデアです。ググってもろくなのが出てこない。実装のサンプルはzennのURLを貼っておいたので、これで見てください。ググると、たぶんこの記事が先に出てくると思います。

Context Closure実装方法ですが、3行で済みます。なので、すべてのページのものを先に実装しておくのもまったく現実的なセンです。

(スライドを示して)かつ、例えばContextの中だけで利用できるコマンドみたいなものも追加できるので、ここに一番メリットがあるんじゃないかと思います。

cy.url()、このContextに入った時にURLにRegisterが含まれているかどうかを検証することもできます。そうすると、テストコードのバグでRegisterPageにいなかったような時にちゃんと落ちてくれるし、わざわざ毎回この検証を書かなくてもいいメリットがあります。

最後にContext Enclosureを紹介してみました。利点は今説明しましたが、欠点としてこの間僕が考えたばかりなので、あまり枯れたアイデアじゃない。今日はほとんどこの話をしに来たので、何かフィードバックがあれば教えてください。

テストコードの読み書きに悩まないためにリーダビリティに気を遣う

というわけで、軽くまとめに入ります。悩まずにテストコードを読み書きするために、リーダビリティに気を遣いましょう。そのために、今日はセマンティクスとコンテキストという2つを紹介しました。また、セマンティクスにはアクセシビリティの要素が非常に強く関連していることがわかりました。

というわけで、ユーザー目線で文脈が明確なテストコードを書いていきたいものですねということで締めたいと思います。ご清聴ありがとうございました。