Testing Library のクエリの優先度をちゃんと理解したい
やること
testing-library を使うときのクエリの優先度を書いている。
自分は優先度を意識してやってるつもりだけど、レビューのときに「ここに書いてるでしょ」だと説得力に欠ける。
ここに書いていく
testing library の基本方針
使うライブラリがどういう思想をもって作られているかを知ることは大切。
基本方針がここに示されてる。
The more your tests resemble the way your software is used, the more confidence they can give you.
(日本語訳)
ソフトウェアのテストが使用方法に似ているほど、テストの信頼性が高くなる
testing library 自体もなるべく使用方法に近いようにテストできるような機能を提供している。 testing library が実装しているユーティリティの基本方針は次の3つ。どれも「このライブラリが極めて軽量で、シンプルで、理解しやすい」を満たすことを目的としている。
- コンポーネントのレンダリングに関するテストをするならば、コンポーネントインスタンスではなく DOM ノードを扱うべき。コンポーネントインスタンスの使用は推奨していない
- コンポーネントの意図した通りの方法で使用するようなテストができるよう促すべき。
- ユーティリティの実装や API はシンプルで柔軟であるべき。
このライブラリを使う我々も、ユーザが実際に使うケースに似たテストを書くように努めたほうがよい。クエリの優先順位は、その思想の下でつけられている。
コンポーネントのレンダリングに関するテストをするならば、コンポーネントインスタンスではなく DOM ノードを扱うべき。コンポーネントインスタンスの使用は推奨していない
例えば Button
というコンポーネントのテストをするときに、
let button = <Button />;
みたいな使い方を推奨してなくて、
render(<Button />);
としましょうね、という話でいいのかな
コンポーネントの意図した通りの方法で使用するようなテストができるよう促すべき。
例えば「ユーザが『送信』ボタンをクリックすると○○になる」みたいなテストしたいとする。「『送信』というボタンを探して、それをクリックする」という挙動を再現したテストを作れるような環境をちゃんと用意しておくよ、よいう話でいいと思う。
ライブラリを使用する我々は、このように用意してくれている環境をちゃんと利用したテストを書きましょうね、ということ
大きな括りにおいての優先度は次の感じ
- 視覚、マウス操作、支援技術を使ったユーザ体験を反映したクエリ(Queries Accessible to Everyone)
- HTML5 と ARIA に準拠したクエリ(Semantic Queries)
- TestIDs を利用するクエリ(Test IDs)
優先度が付いている理由は、前述の「ユーザが実際に使うケースに似たテストを書くように努めたほうがよい」である。優先度が低いものはユーザの体験を反映しづらい。
3の優先度が低い理由は、ユーザが認識できない情報を使っているから。
2の優先度が低い理由は、これらの属性を使った場合のユーザ体験はブラウザや支援技術によって大きく異なるから。
Queries Accessible to Everyone について
このカテゴリでの優先度はこんな感じ
- ロールをもとに element を探す
getByRole
- ラベルをもとに element を探す
getByLabelText
- プレースホルダーをもとに element を探す
getByPlaceholderText
- テキストをもとに element を探す
getByText
- フォームの入力された値をもとに element を探す
getByDisplayValue
ラベルをもとに element を探す getByLabelText
getByRole
の次に優先度が高い。
getByLabel("name")
は「"name" というラベルにくっついてる入力欄を探す」みたいなことができる。
これと同じ表現は getByRole
でも getByRole('textbox', { name: "name" })
とできる。なおかつ role の種類まで指定できるので、 getByRole
のほうが優先度が高いとしていい。
オプションは ここ にある
getByRole
ではなくて getByLabelText
を使う必要ってどういうケースだろう。 getAllByLabelText(/value/)
とかするとロールに関わらずすべての "** value" というラベルの element を見つけられる、みたいな使い方かな
プレースホルダーをもとに element を探す getByPlaceholderText
プレースホルダーしか element を特定する方法がないときは他のやり方よりもマシなので使ってもいいよ、くらいの感じ
プレースホルダーは有害である という記事へのリンクがある。
ラベルをなくしてプレースホルダーだけを使って入力欄を作る危険性について述べている
挙げられている問題
- 入力中に何を書くべきか忘れたとき、入力した文字を消してプレースホルダーを表示させないといけない
- 送信前に入力内容が正しいか確認できない
- エラーメッセージが何に対して書かれているのかわからなくなる
- フォームに注目するだけでプレースホルダーが消える場合、 Tab キー移動した際に何を書いていいかわからない(注目するまでフォームが画面外にいることがある)
- 空のフォームよりも目に止まりにくい
- プレースホルダーを初期値と勘違いしてしまう可能性がある
- ユーザが入力された値と勘違いし、自身が入力するのに不要な手間が必要だと誤解させてしまう
「プレースホルダーしかないから getByPlaceholderText
を使う」という状況が良くないので使わないほうがいい。
getByRole
でも getByPlaceholderText
でもどっちでもいいなら、あえて後者を使う理由は見当たらない。
getByPlaceholderText("〇山△子")
よりも getByRole('textbox', {name: "名前"})
のほうがやりたいことはよくわかる
テキストをもとに element を探す getByText
インタラクションがない element に対して使う。
たまに、 getByText("送信")
みたいなことをやってしまってたんだけど、そういう場合には getByRole("button", {name: "送信"})
としたほうがいい
フォームの入力された値をもとに element を探す getByDisplayValue
The current value of a form element can be useful when navigating a page with filled-in values.
とあったけど意味わからなかった。
とりあえずあんまり出番はないと思っていいかな(使ったことない)
Semantic Queries について
優先度は
-
alt
のアトリビュートを使うgetByAltText
-
title
のアトリビュートを使うgetByTitle
alt のアトリビュートを使うgetByAltText
alt
を使って element を探す。
img
、input
、area
にしか使えない。
ByRole の説明の中に、
For example
<img aria-label="fancy image" src="fancy.jpg" />
will be returned for bothgetByAltText('fancy image')
andgetByRole('img', { name: 'fancy image' })
「aria-label
属性を使うと getByRole
でも getByAltText
でもどちらでも探せるようになる」と解釈したんだけど本当?この場合 getByAltText
は動かないっぽいんだけど...
render(<img aria-label="fancy image" src="fancy.jpg" />);
screen.getByRole("img", { name: "fancy image" }); // ok
screen.getByAltText("fancy image") // error: Unable to find an element with the alt text: fancy image
alt
アトリビュートを指定するとどちらでも動く
render(<img alt="fancy image" src="fancy.jpg" />);
screen.getByRole("img", { name: "fancy image" }); // ok
screen.getByAltText("fancy image"); // ok
テストが落ちる
title のアトリビュートを使う getByTitle
title
アトリビュートを参照しないスクリーンリーダーがあるかららしい。また、画面を見るユーザにも見えない情報である。この2点が title
じゃなくて aria-label
なんかを使って情報を渡したほうがいい理由。だから getByLabelText
とかを使おうという話。
SVG もタイトルを指定できるらしい。知らなかった
この場合 getByAltText は動かないっぽいんだけど...
よくわからないので質問した。こういうの投げるの初めてでドキドキした
マージされてうれしい
Test IDs について
テストでしか使わない ID を使うので、ユーザの動きを再現しているとは言いづらい
テキストが動的な場合に使えると書いているけどどういう意味だろう、よく分からなかった
一通りクエリをさらった感想
testing library の優先度は「ソフトウェアのテストが使用方法に似ているほど、テストの信頼性が高くなる」という基本方針に則ってつけられている。
クエリを選ぶ場合は「どっちえも動作が確認できているから大丈夫」ではなく「こっちのほうがユーザの使用方法に近くなるから」という理由で適切なクエリを選ぶのが、いいテストを書くことにつながる。
あと優先度が低いテストを書いているならそもそもコンポーネントのアクセシビリティが悪い、ともいえる。
テストがないコンポーネントに対しては、今の段階で可能な最も高い優先度のクエリのテストを作り、そのあとに実装を修正し、再度テストを修正するのがよさそう