見出し画像

Seleniumで失敗する前に頭に入れておきたい話@2021/5/30

どうも、どうも。

最近、また、Seleniumに引き込まれてしまった、おれです。

2013年くらいだったかな、そのあたりから、断続的にSeleniumに関わってしまっています。

Seleniumはやり過ぎないほうがいいです。でも、やらざるをえない状況の方が、なるべく沼にならずに済むようにしてあげたい。そんな思いでこの記事を書くことにしました。


Seleniumはちょっとだけやるならいい、でも、調子に乗ってやり過ぎると後悔しがち

やり過ぎるケースとしては、やっぱり、結合テストを自動化したい場合でしょう。でも、普通にSelenium WebDriverとかを使って、1つ1つ手で書いていたら、マジで死にます。かと言って、Selenium IDEを使っても、メンテが手間で、爆死です。一般の方は、とにかく、量を減らすこと、一気に増やしすぎないことを考えて下さい。

Seleniumをやり過ぎてしまう理由は、費用対効果が見えづらいからだと思います。テストケースを実装している途中では、まだ、費用対効果云々と言われず、順調なように錯覚してしまいますが、いざ、一通りできた時に色々な問題に気付きます。

手作業はすごいよ!

従来型の手作業での結合テストですが、すごいです!まず、頭数そろえればできます!見通しもいいです!リーダーにあとどれくらいで終わるか聞いたら、結構な精度で答えをもらえます!だいたい当たります!

一方で、Seleniumで自動化した結合テストは・・・、リーダーにあとどれくらいで終わるか聞いても、口ごもってます。そもそも、自動化したはずなのに、動かなくなってる?使えてない?いっぱいお金かけた分、あの努力はどこ行った?

Seleniumコンサルタント、的な人々

あの方たちは、大した量、書いてないような。。Seleniumをメシの種にしてるだけで、雲行きが怪しくなってくると、渡り鳥のように、次の所に行くだけなのかな、と。いや、でも、悪気はないのかもしれません。単に生活がかかってるから、やらざるをえない、ということかと。

Seleniumのテストスクリプトは資産のようで負債的な側面がある

まず、基本的に、この視点を持ったほうがいいです。一度書いたやつを永遠使えるってものではないからです。メンテしていかないといけないわけで、ローンのように、定期的に我々のお財布から予算を抜いていきます。それが負債という意味です。なるべくメンテが要らないように書くべきなんでしょうが、相手にしてるシステムなどの都合で、そうも行かないこともあります。

Seleniumは開発チームがやったほうがいいよ

開発チームがやらないケースってのがあると思います。理由は、テストスクリプトをたくさん書きすぎて、手が回らなくなるからです。でも、開発チームが自ら書くことで、「テストしやすいシステムとは何か?」というのを考えて、システム本体にフィードバックできて、いいシステムになります。切り離すと、そういうのが無くなって、自己満足だけの、ダメなシステムになります。

「むしろ切り離したほうが、ブラックボックステストができてイイ!」とかなんとか言ってる、大手テスト会社さんとかも居ますが、それは、ただのポジトーク(宣伝文句)です。ブラックボックステストって言われると、何やら、それらしい、オトナ感を醸してますが、別の言い方を言うと、効果的ではないテスト、すぐ使えなくなるテスト、とも言えます。

でも、すぐに使えなくなったほうが、メンテが必要になって、売上UPですから、テスト会社さんにとっては悪くない話です。しかし、やはり、テスト会社さんの中でも、良心がある人は、辞めていきます。

Seleniumのプロだってイヤになる

大量にテストスクリプトを手書きさせられていると、行き詰まるのが早いです。いくら、Seleniumを使いこなしていても、メンテ不可能な量にまでテストスクリプトが膨らんで、費用対効果云々を言われると、居心地は良くないです。そして、居なくなります。すると、また、Seleniumルーキーにバトンタッチするだけで、エンドレスループで、お金だけが浪費されていきます。

Seleniumのテストスクリプトが使えなくなる理由

テストスクリプトを書いた後で、それが動かなくなることがあります。理由は以下です。

1. ブラウザのバージョンが上がったから

ちょっと挙動が変わることがありえます。勝手に上がらないようにするには、Firefoxだと、Firefox ESRっていう、バグフィックスしか入らない、安定版があります。あ、ただし、Firefox万歳!って状況ではなくなってきてるかも。。また、別にこの件は書きたいですが、Chromeと比べて動きが素直じゃないです。前はFirefoxのほうが素直だった気がしますが、Googleさんががんばったのかな。

Chromeだと、Chromiumっていう、ロゴなし版があります。RedHadに対するCentOSみたいなやつです。ただし、Chromiumのビルドって、なんか、野良ビルド感がありますね。Googleさんがビルドしたやつじゃないですよね?なんとなく怪しい。。使えますけど。。あ、ただし、やはり、出所不明のアプリとしてSkyseeとかにブロックされたりします。

2. Seleniumのバージョンを上げたから

Seleniumも進化しています。その進化を取り込んだ結果、一部が動かなくなる、ということもありえます。

3. テスト対象のシステム本体の仕様変更があったから

「前は、ここにこういう画面要素があったはずなのに、無くなった!」とかそういうやつです。開発チームがテストスクリプトを書いてれば、当然、気付くんでしょうけど、切り離していたら、なぜ、動かなくなったのか、把握するのが遅れますね。

4. テスト対象のシステムの内部状態が変化したから

内部状態がこういう時だけ出る画面要素、出ない画面要素がある、とか、そういうケースです。開発チームがテストスクリプトを書いているのであれば、そのあたり、直接、backendをいじるとかして、なんとかしてくれるかもしれませんけど、チームを分けていると、そういうのができなくて、「やれるのはブラウザ経由だけです。backendとかいじらせるわけないでしょ。」とかなって、テストスクリプトを書くだけでなく、事前の設定をする部分も、Seleniumなどを使って別途実装しないといけなくなったりします(回りくどい)。

5. 「たまたまうまく動く」ようにしか書いてなかったから

「5秒待機すればOKだと思ったから、そう書いたけど、場合によっては6秒かかる」とか、「ここのリストの最初のやつを取ればOKだと思っていたけど、いつも最初に出るとは限らない」とか、そういうやつです。


上記のいずれかの原因だって、すぐに分かればいいんですが、切り分けるのも大変ですよね。

Selenium IDEは、アリ?ナシ?

どうなんですかね。ぶっちゃけ、自分は、Selenium IDEは採用してません。隣のチームの人が使っていたりして、大体、どんな感じになっているかだけは分かってますが。。

大体はできるんでしょうけど、完全に自由、ではないですよね。Selenium IDEでできないことは諦める!っていう合意が取れていれば、いいのかもしれませんけど、もし、合意できたとしても、思った以上にできないことが多かったり、箇所としては少なくても、ここだけはできてほしい!って所ができなくて困ったり、とかないですかね?

そもそもが、生成されるやつが、汎用プログラミング言語で書かれてないですよね?あの専用のJSON形式に最適化した人材とかになりたくないなぁ。。マジで、Seleniumの沼から抜けようにも、抜けられなくなりますよ。

以前は、Javaのコードとかの生成もできてましたが、最近のは、Selenium IDE専用のJSON形式しか無いような。。まぁ、以前、生成できてたJavaコードもべた書きのやつで、メンテもきつそうでしたが。。

Page-Objectパターンはご存じですよね?

今はもっといいアプローチが、もしかしたら、あるのかもしれませんけど、Seleniumで自動化する際の、典型的なデザパタのことです。要するにオブジェクト指向なやつです。以前、Selenium IDEで生成できてたJavaコードは、Page-Objectパターンではなく、手続き型スタイルってことです。今、Selenium IDEで生成できるJSON形式のやつも、Pageクラスとかもちろんあるわけないので、手続き型スタイルです。

もちろん、Page-Objectパターンにさえなっていれば、オールOKなのかと言ったら、そうではないです。Page-Objectパターンで書くのはよくあるやつ、っていうのは当然理解していて、書くんだけど、ここまでに書いてきたような、いろいろな問題があるってことです。そもそも、Page-Objectパターンすら知らないような人だったら、もっと怖いです。

テストの実行にかかる時間

自動化したら速いでしょ?って思われるかもしれませんけど、量が多いと、一晩かけても終わらない、なんてこともありえます。多くても、複数環境作って、並行で回すとか、やりようはあるかもしれませんけど、予算とかもありますから。。

それに、テストスクリプトをメンテしてる人の気持ちになって考えてみてほしいんですが、うまく動かなくなった箇所を日中に直したとします。それの正式な結果は、翌日にならないと分からないとします。手元のPCでも動作確認はもちろんしますけど、環境の違いとかで、手元のPCと正式な結果が異なることがありえます。そうすると、何が違うのかを考える必要がありますし、昨日考えて終わった!と思っていたことを、また、思い出して、考えないといけないです。なかなか、仕事が片付いて行ってる感じがしないです。

それに、手元PCで動かす際も、テストケース1個で2~3分とかかかるやつも普通にありえます。2~3分くらいだと、他に平行して何かやるほどの時間でもないので、そういうのを、ぼーっと見てる時間もあります。1回で直らなくて、何回も試行錯誤で直して、何回も2~3分かけてぼーっと見てることもあります。そして、テストケースは他にもたくさんあります。そうなると、思った以上に時間使ってますよ。見ていることで気付くこともあるので、必ずしも、無駄な時間ではないですが。。

そういうわけで、何かと時間がかかります。

Seleniumより単体テストがんばったほうがいいよ

単体テストだと実行時間はめちゃめちゃ速いです。なるべく単体テストにして、単体テストでやるようなことをSeleniumにやらせないほうがいいです。でも、開発チームと切り分けると、テストって聞いたらテストチームに押し付けて、そういう妙なことをやらされるケースがあり得ます。「これは単体レベルでしょう!」って思うんだけど、自分がテストを実装する側の会社だったら、売上にはなるんで、変な仕事だけど、やっておくか、みたいになります。

ヘッドレスブラウザ、ご存じですよね?

ヘッドレスブラウザっていうのは、画面がない、見えないブラウザってことです。画面がない分、効率いいでしょう。夜間バッチで回す時とかは、見る人とか居ないので、ヘッドレスのほうがいいでしょう。

以前は、ヘッドレスブラウザに何を使うか?という問題もあったと思うんですが、今は、FirefoxやChromeのヘッドレス版があるので、それを使えばいいと思います。メインで使ってるブラウザのヘッドレス版にすればよいです。

PhantomJSとかは使う理由無くなってると思います。FirefoxやChromeと動きの違う箇所もあるでしょうし。。これは、FirefoxやChromeのヘッドレス版が無かった時代にどうしてもヘッドレスじゃないと困る人が使ってたやつだと思います。もしくは、自動テストというより、クローラーとか、全然別な用途で使ってた人には、これでも良かったんでしょう。

待機処理、どう書いてますか?

Time.sleep(3000)とか、固定秒数で書くやつ、マジでやめたほうがいいです。まぁ、ほんのちょっとしたことに使ってるだけなら、それでもいいですが、大量のテストスクリプトを書いてるって状況で、そのスタイルだと、そのうち、「3秒じゃダメだな、5秒だな」「5秒じゃダメだな、10秒だな」とかなんとか、どんどんお手軽に引き延ばされていって、テスト時間が膨大な長さになってしまう主原因となります。実際には2秒であるべき状態になるケースが多くても、いつも10秒待ってしまうので、8秒無駄に長くなってしまう、ということです。

じゃあ、どう書けっていうんだ?って、思われるかもしれませんけど、「○○の条件が成立するまで、最大××秒待機する」って、そういう形態のやつがあるんで、それ使ったほうがいいです。wait.until(・・・) みたいな書き方になります。面倒に思われるかもしれませんし、実際、面倒ですが、良いテストにするには、しょうがないです。

でも、「○○の条件」とか、そんな目印になる条件ないよ!ってケースもあるかもしれません。このへんも、開発チームがやっていれば、そういう条件を用意できたりするんですけどね。テストチームとかに仕事を切り離すと、こういう問題も出てきますね。

そういうの、情報共有してやればいいじゃない?って思われますけど、部署間の壁は想像以上に高いんですよ。部署だけでなく、会社も違うとなおさらです。みんな、コミュニケーションとか摩擦を面倒くさがって、それをやらずに固定秒数のオンパレードで爆死・・・。あるあるパターンじゃないですか?

暗黙的待機処理 vs 明示的待機処理 ご存じですよね?

今、上に書いた、sleepとかwait.untilとかは明示的のほうです。「ここで待機処理必要でしょ!」ってプログラマが判断して、個別の箇所に差し込むやつです。

一方で、暗黙的待機処理ってやつもあります。driver.implicitlyWait(5, TimeUnit.SECONDS)みたいに書くやつです。これは、driver.findElementする時に、最大で何秒までなら待ってやるか、あらかじめ、WebDriverに指定しておくやつです。ただし、これを長くし過ぎると、サイトがエラーだらけでどうしようもない時でも、結構、待ってしまって、「一晩かけても、テスト終わってなかった!」みたいになってしまうケースもありえます。

あと、この値をちょくちょくいじってると、全体の結果が結構、変わってくる、ということもありえるので、注意が必要です。おっさんは、16秒とかにしてたかな~。普通のシステムなら、そんなに要らない気がするけど。。5秒くらいから始めて、手軽に済ませたいならMAXで16まで上げることもありうると考えて、もし、16秒でたまにでも失敗するような箇所があったら、そういう箇所はwait.untilにしたほうがよさそうですね。wait.untilに渡す条件は自分で作ったりもできるので、なんか特殊な待ち方しないといけないやつも、それでできるはずです。

テスト環境をリセットとか、できたらいいな

上のほうに書いた、テスト対象システムの内部状態の変化に関わる部分です。これをいったんリセットして、いつも決まった所から開始できれば、問題の切り分けがしやすいです。テスト対象システムを丸ごと一式、Dockerイメージにしてあって、それを展開しなおすだけで、いつも元通り、とかだと気持ち良さそうですね。まぁ、いろいろな部署が作ってる、色々なシステムが複雑に絡んでたりして、全部をDockerイメージに入れるなんて無理、っていうのもあるかもしれませんけどね。できたらいいな、の話です。

画面への到達確認、ちゃんとやってますか?

そもそも、画面に来てないってことなら、その時点でエラーにしてほしいんですよね。たとえば、A -> B -> C っていう画面遷移をするシステムがあるとして、Cの画面のテストでエラーが出ていて、そのエラーの内容が、全然意味の分からないエラーだったりします。実は、そもそも、Cの画面に来てなくて、Bの画面にすら来てないのに、Bの画面のPageクラスの実装担当者が、Bの画面への到達確認の実装をちゃんと書いてないもんだから、Cの画面に来たつもりで、Aの画面に対してテストをしてて、あるわけない画面要素を見付けようとしてエラーになってる、ってことです。

まぁ、このケースだと、各Pageクラスにそれぞれ、到達確認処理を書けるようになっているってことなので、まだいいですが、そもそもが、A、B、Cすべての到達確認を、Cの画面のテストの実装担当者が全部書かないといけないんだとしたら、そりゃ、面倒ですよね。サボりたい気持ちも分かります。それは最悪のケースです。でも、そうなってる会社も結構多いんじゃないですか?まぁ、Selenium IDEとか使ってたら、当然、そうするしかないでしょうけど。。

画面を簡単にユニークに特定できる方法が欲しい

たとえば、画面ごと、もしくは、画面のコンポーネントごとに全部、ユニークなIDが付いていて、それだけ見れば、各ページへの到達確認ができるとラクですね。できれば、P001みたいな、簡単にページを識別できるIDだと、それをそのままPageクラスの名前にも付けたりできて、画面との対応関係も分かりやすくてなおいいですが。。こんな単純なことでも、開発チームと分かれていると、やれなくて、画面の中で見えている文言から特定とか、そういうことになりがちです。でも、その文言がユニークとも限らないですからね。

ページ全体の読み込みの完了も確認できたらなぁ

欲を言えば、その要素を見るだけで、ページ全体の読み込みが完了していることも確認できる、とかだとなおいいですが。。最近、後から読み込まれるやつとかもあるじゃないですか。画面ごとに最後に読み込まれるものが違ってると、確認すべきものも違ってくるので、面倒そうですね。

クロスブラウザ対応やりますか?

ブラウザは、絞れるなら絞ったほうがいいですよ。Selenium Gridとかなんとか言ってる人も居ましたけど、あれは、各ブラウザにテストスクリプトをディスパッチできる、ってだけですよね?「各ブラウザの異なる挙動を吸収して、おれの書いたテストスクリプト1つでよろしくやってくれる」とか、そういう話じゃないですよね。もし、ブラウザごとの、そういう差があれば、Pageクラスのベースクラスで、ブラウザごとに実装を切り替えるようにしておくとか、そういう対応が必要になりますよね。

もちろん、テスト会社さんとしては、クロスブラウザ対応、やりたいと思います。だって、その分、手間がかかって、売上UPですから。。クロスブラウザ対応、どんと来い!って感じで自身ありげに見えるかもしれませんが、ご予算とか、ちゃんと考えたほうがいいですよ。もちろん、ブラウザが2種類になったからと言って、費用が2倍になるわけじゃないですが、1.4倍くらいにはなるかもしれません。そもそもが、Pageクラスとか使ってない形態だったら、マジでテストスクリプトの量が2倍とか、アホなことしてる会社もあるかもしれませんからね。それだとマジで2倍です。お気をつけください。

Pageクラス、生成してみませんか?

Pageクラスを書くのが負担だから、手続き型スタイルでやってる人、多いのかもしれませんけど、もし、Pageクラスが生成できるとしたら、どうですか?そうなると、手書きしないといけないのは、Pageクラスを使う側の処理だけになりますね。もちろん、Pageクラスを全て生成しきるのは難しいかもしれません。

たとえば、XxxBasePageクラスっていうものを生成するとして、そこに、locator(XPathやCSSセレクタ)とかを書いてあって、セレクトボックスや、チェックボックス、ラジオボタンも、それぞれ1セットごとにクラス化したものを生成してあって、それをXxxBasePageクラスが持つようにして、XxxBasePageを継承したXxxPageクラスっていうのがあって、手書きの処理はXxxPageクラスに書いて、XxxPageクラスを使うのは、全体の流れを記述するScenario系のクラス、という感じです。

数年前、不完全ではありますが、そういうのをやってたことがあります。最近、また、動員されたので、これをさらにブラッシュアップした感じでやってますが、1人で1か月半ぐらいですが、一通り生成できるようになりましたよ。一度できてしまえば、この生成処理を他のプロジェクトでも、ちょっと調整して使いまわせそうです。

まぁ、それをやるには、手書きで書いたら、どうなる?っていうのが、あらかじめ、分かってないと難しいでしょうけどね。どうしても、まとまった分量の自動化をやらざるをえない状況の方は、検討されてみてはどうでしょうか?

あ、HTMLをDOMパーサーで読み込んで、<input type="checkbox">ならどうこうするとか、そういうのを書く感じですよ。Selenium IDEのソースも見て、参考になりました。Selenium IDEでは、画面要素をクリックすると、そこのlocatorを出してくれるじゃないですか。あの部分です。あそこの実装、参考になります。私は、CSSセレクタではなくて、XPathで書く派なんですが、XPathをどう組み立てるかも、あそこに書かれてますよ。

後から読み込まれるコンポーネントはつらいかも

最近はよくあるのかもしれないですけど、つらそうですね。ラクなのは、ある段階では、HTMLが全部揃っていて、それが表示か非表示かの違いがあるだけ、だと、Pageクラスは生成しやすいですね。人が見たら、基本的には同じと認識するページでも、DOM構造からして微妙に違ってたら、一度、HTMLをパースして、それを元に生成すれば、OKとはならないですからね。DOM構造が変わるたびに、そのHTMLのスナップショットを撮って、それぞれの段階から、生成しないといけないです。まぁ、全部手書きでやるって人には、さほど気にならない所かもしれませんけどね。このへんも、開発チーム自らSeleniumをいじってれば、生成しやすいように工夫できそうですが。。幸いにも、今の所、私は、この手のサイトに対してPageクラスを生成する、という場面に遭遇してないですが、後で、こういうケースに遭遇しそうです。

あとがき

いや~、どうだったでしょうか?Seleniumの袋小路にハマり込まずに済みそうですか?死なない程度にやって、軽めに済ませたほうがいいですよ。人生には、もっと楽しいことがたくさんあります。笑える時代を作ろうや!では、ごきげんよう。

この記事が気に入ったらサポートをしてみませんか?