QAエンジニアの異常な愛情〜または私は如何にして心配するのを止めてCodeceptJSを愛するようになったか


はじめに

今から約1ヶ月前の2019年4月1日に、TestCafeで記述したE2Eテストを全て削除し、全面的にCodeceptJSに切り替えました。

File Changedが大変なことになっていますが、基本的にはディレクトリ構造を変え、重複していたコードを削除しただけですので大した内容ではありません。
(お作法とかはギュッと目をつぶって頂けると助かります)


自分がTestCafeを使い始めたのは、今からおよそ1年前、2018年6月頃でした。
SeleniumやPuppeteerなどのメジャーなライブラリにはない様々な特徴が気に入り、およそ半年ほどの利用を経て、2018年12月のOPENLOGI Advent Calendarでは、『個人的ベストE2Eフレームワーク"TestCafe"の紹介』 という記事を書きました。

それからさらに1ヶ月ほどが経ち、正月休みで暇を持て余していた自分は、何を思ったか Awesome Selenium で紹介されているライブラリを片っ端から試していくという荒行を開始しました。

その時に出会ったのがCodeceptJSです。
受け入れテストをそのまま動かすことを強く意識した特徴的な記法を受け入れるのに当初は時間を要しましたが、使っていくうちに、テストを読みやすく効率的に書くための様々な機能に惚れ込み、残っていた休暇と、休暇明けからしばらくの土日は全てCodeceptJSの検証作業に費やしました。
余談ですが、検証中に得られた知見をQiitaにアウトプットしまくったところ、一時的にOrganizationランキングの上位に弊社がランクインしていました。

検証の結果、いくつか実運用上問題のあるバグが存在していたため、かなり小粒でしたがいくつかPullRequestを出し、mergeされました。
(これまた余談ですが、これが自分にとって初めてのOSSコントリビュートでした)
テストケースの移行に約1ヶ月、ライブラリのバグの修正や、CIのメンテナンス、テストの安定性の向上などに約2ヶ月ほどを要し、計3ヶ月ほどでTestCafe→CodeceptJSへの乗り換えが完了しました。


移行を決めた段階で、TestCafeで書かれたテストケースは20件ぐらいで、移行にそれほど長い時間がかからないと想定され、実際、実業務の片手間でコツコツすすめて3ヶ月で完了するといったレベルでした。
とはいえ、既に実稼働しているテストコードを破棄し、全く設計思想の異なるライブラリに移行することはかなりの勇気と情熱が必要です。
また、テストコードそのものの移行だけでなく、ライブラリの設定や、CIなどの実行環境構築も必要でしたので、当初想定していたより時間がかかりました。

なぜ、それほどの手間をかけてまで、ライブラリの変更という決断に至ったのか?
というわけで今回は、TestCafeが大好きだった自分が、なぜCodeceptJSに乗り換えたのかについて書きたいと思います。

※かならずお読みください※

この記事は2019年1月ごろまでのTestCafeの利用経験を元に書いていますが、当時まだTestCafeはパブリックベータ版でしたが、その後、2019年2月に正式版がリリースされています。
本記事で紹介している様々な不具合や仕様上の問題点については、現時点では改善されている可能性があります。
そのため、TestCafeの利用を検討している方は、 絶対にこの記事を鵜呑みにせず 、ご自身の手で検証した上で判断してください。

そもそもなぜTestCafeを使おうと思ったのか

先述した記事『個人的ベストE2Eフレームワーク"TestCafe"の紹介』でも書きましたが、

  • 環境構築が容易であること
  • モバイルも含めたクロスブラウザテストが手軽に出来ること
  • 要素が表示されるまで待ってくれること

この3点が大きなポイントでした。
フレームワークの選定にあたって、CypressWebdriverIOなどの他のライブラリと比較したのですが、Cypressはクロスブラウザ、WebDriverIOは環境構築や表示待ちの面で要件を満たせませんでした。
(WebDriverIO、環境構築かなり楽な方だと思うのですが、なぜか当時はSelenium-Standaloneが上手く動作せず……)

なぜTestCafeをやめようと思ったのか

Seleniumでも意外と色々できることを知った

実を言うと、E2Eテストライブラリを比較検討した際、Seleniumはかなり早い段階で候補から外してしまっていました。
Selenium RC時代の苦い思い出もあり、なんとなくレガシーで、SPAには不向きで、環境構築が辛いといったネガティブなイメージが先行していました。

ですが、あるとき SelenideGeb の存在を知り、適切なラッパーライブラリを使うことでSeleniumの欠点をカバーし、SPAのテストもストレスなく実行できる出来ると知りました。
また、環境構築についても、公式で配布しているDockerImageや、Selenium-StandaloneのようなNPMパッケージを利用することでコマンド一発で環境構築が出来ると知りました。

このことで 何となく とか イメージ とか 過去の苦い思い出 とかで技術を評価するのは良くない」ということが身にしみたので、Seleniumも含めていろいろなライブラリを再評価しようとしたのが、TestCafeをやめようと思ったそもそものきっかけでした。

ブラウザ操作部分の実装が枯れておらず、地雷を踏むことが多かった

また、E2Eテスト実装においてコアとなる「ブラウザ操作」部分の安定性において、TestCafeはいまいちだと思うケースがありました。

どういうことかというと、TestCafeのブラウザ自動操作は、WebDriverではなく、URL rewriting proxyという独自実装で実現されています。
https://devexpress.github.io/testcafe/faq/#i-have-heard-that-testcafe-does-not-use-selenium-how-does-it-operate

この方式を取ることで、TestCafeは

  • 環境構築に特別な準備がいらない(WebDriver不要)
  • モバイルも含めたクロスブラウザテストが可能
  • 要素の表示待ちを自動で実行してくれる
  • 実行が早い(らしい、あんまり実感したことはない……)

などの機能を実現しており、いわばTestCafeの最大の特徴であり根幹となっている部分なのですが、一方でこの部分が枯れていないがゆえに、変なところで不具合に遭遇することも非常に多かったです。
例を挙げるとこんな感じです。

  • Edgeの最新バージョンでTextareaへの文字入力ができない など、ブラウザのバージョンアップに伴いバグが出ることがあった
  • 縦横のスクロールが可能なサイトで、スクロールが上手くいかず要素のクリックに失敗することがあった
  • 手動でブラウザを操作しているときは普通に動作するのに、なぜかTestCafe経由では謎のエラーで画面描画すらされない

その点、Seleniumで用いられるWebDriverという仕組みは、各ブラウザベンダーが対応するWebDriverを開発・メンテナンスしているため、ブラウザ操作部分においては信頼性が高いという利点があります。

Seleniumのエコシステムを活用できない

TestCafeは非Selenium系のライブラリですので(Selenium or not Seleniumみたいな区分けもアレだと思いますが)、たとえばコンテナによる分散実行をサポートしてくれるZaleniumSelenoidなどを利用することができません。

現在、TestCafeはVideo Recordingをサポートしていますが、自分が使っていた当時はまだ未実装で、失敗したテストケースのログを後から追うのが非常に苦痛でした。
上述のZaleniumを使えれば、自動でセッションごとの動画が記録されるのですが、こうした便利なツールが利用できないことも残念なポイントでした。

CodeceptJSとの出会い

手前味噌で恐縮ですが、以前『エンジニア視点で見たCodeceptJS』という記事をQiitaに投稿したことがありました。
この記事はこんな見出しで始まります。

CodeceptJS?ああ、あのキモい書き方のやつでしょ?

何を隠そう、「はじめに」で書いた Awesome Selenium を片っ端から試すという荒行の過程で、CodeceptJSは書き方がキモいという理由で試すのを後回しにしてしまっていました。
ですが、実際試してみると、TestCafeを使いはじめた動機となった

  • 環境構築が容易であること
  • モバイルも含めたクロスブラウザテストが手軽に出来ること
  • 要素が表示されるまで待ってくれること

が全て満たせるということと、ブラウザ操作部分をSeleniumやPuppeteerなどのライブラリに任せることで、不具合が少なく動作が安定するという利点がありました。

また、当初はなじめなかった独特の記法も、慣れてみると非常に読みやすく、メンテナンス性が高いと思うようになりました。

  • 受け入れテストのテストスクリプトを、そのままテストコードに落とし込むことができる
  • 同期的に書くことができ、 await を書く必要がない
  • Semantic LocatorによりCSSセレクタやXPathから解放される

このように、受け入れテストを効率よく自動化することに徹底的にこだわった姿勢を自分は非常に気に入りました。

導入までのプロセス

TestCafeで書かれたコードをCodeceptJSベースに移植した

もともと、TestCafe導入初期に書かれたコードはかなり試行錯誤しながら書いていたので、単純な書き換えでは技術的負債もそのまま移植することになるのが目に見えていました。
その為、リリース前に手動で実施しているリグレッションテストの項目を元に、ほとんどのテストを再実装しました。
移行期間中は、CIでの実行はCodeceptJSのみ行い、リリース前日のリグレッションテスト実行の際にCodeceptJSとTestCafe双方でテストを実行する、という形式を採用しました。

CIツールの準備

TestCafe時代からGitLab CIで定期実行をしていたので、基本的な部分は変わらなかったのですが、CI内でdocker-composeを利用するために、Docker-in-Docker(dind)などの利用が必要でした。
下記の記事を参考にしました。
https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#making-docker-in-docker-builds-faster-with-docker-layer-caching

導入の結果

テスト実行が安定した

E2Eテストにつきものの「よく分かんないけどしょっちゅうテストが落ちる」という状況は、CodeceptJSに移行してからかなり改善されました。
retryFailedStepという公式プラグインがあり、クリックやアサーションなどのステップが失敗した際、自動でそのステップのみ再実行してくれるのですが、例えば「クリックしようとした要素に通知トーストが被さっていて実行に失敗した」などでテストに失敗するケースがかなり削減されました。

コード量が削減された

TestCafeでは、「ある文字列を持つ要素」を選択するためには、次のような記述をする必要がありました。


const button = Selector('button').withText('送信')
await t.click(button)

const field = Selector('input').withAttribute({placeholder: '検索'})
await t.typeText(field, 'TestCafe')

一方で、CodeceptJSでは、シンプルに文字列を指定するだけで実現できます。


I.click('送信')
I.fillField('検索', 'CodeceptJS')

CodeceptJSにはこの他にも、テストコードの実装を楽にしてくれる様々な機能がある為、コード量が減り、読みやすく書きやすいテストコードが実現できています。

これからの課題

CodeceptJSのユーザー・Contributor数が少ない

OSSを利用する上で、コミュニティが活発かどうかは非常に重要なファクターで、CodeceptJSはこの点でまだ課題があると考えています。
例えば、GithubのStar数で比較すると、2019/5/12時点で、TestCafeは6,716、CodeceptJSは2,208と、約1/3程度です。

そんな事情もあり「とりあえず知名度だけ上げてみよう」と書いたのが、『我が名は神龍……どんなテストもひとつだけ自動化してやろう』という記事です。
ありがたいことに、1400件を超えるいいね!を頂き、現在も少しずつ増え続けています。

公開当時、QiitaのCodeceptJSタグのフォロワーは0人だったのですが、公開後は5人になりました。
0→5なので、増加率的には無限大ですね。大躍進です。

冗談抜きで、おれ自身は大体どこに行っても神龍の人で通じるようになってきたのですが、本来の目的であったCodeceptJSの知名度はそんなに上がってない気がするので、頑張っていきたいです。

とはいえ、まったく状況が改善していないわけではなく、少しずつですが「使ってみた」系の記事が増えるようになってきました。
なかでも、先日公開された @Kesin11 さんの記事『E2Eテストの面倒くさいことはCodeceptJSにお願いしよう』は出来ることが非常によくまとまっており、大変おすすめです。
また、クロスブラウザテストの環境構築まで含めた解説記事としては、別の方の『Vagrant + Selenium + node.js(CodeceptJS)でIE, Chrome, FirefoxのマルチブラウザE2Eテスト』というのもあり、こちらも大変おすすめです。

なお、CodeceptJSはGithubの他、discourseフォーラムやSlackなどで質問を受け付けています。
興味のある方は是非ご参加ください。

テスト実行時間が長い

かなり最適化を進めたとはいえ、約40ケースの実行に30〜40分程度の時間がかかっています。
テスト実行時間は開発効率、開発体験に直結するので、先程も登場したZaleniumなどを用いて分散実行させ、実行効率を上げていきたいと思っています。

ところでTestCafeのことは嫌いになっちゃったの?

いえ、大好きです
導入コストは低く、出来ることは多い、素晴らしいライブラリだと思います。

今後有償になるそうですが、TestCafe StudioというRecord&Playbackツールも用意されています。
Selenium, Cypress, Puppeteerなどと同様、今後もE2Eテストツールの有力な選択肢の一つとして開発が続くことを期待しています。

おわりに

TestCafeとCodeceptJSを通算で約8ヶ月ほど実運用しましたが、実はこれによって不具合を発見したのはたった1件です
E2Eテストは実装難易度が高く、実行時間は長く、それでいてカバーできる領域は少ない、自動テストの中では非常にコストパフォーマンスの悪い分野です。

とはいえ、これが無ければ防げないバグがあるのは事実ですし、これがあることで開発者が安心して開発できるのも事実です。
であれば、出来る限り実装コストが低く、実行時間が短く保てる方法を常に模索し、コストとパフォーマンスのバランスを良くしていくことは、QAエンジニアの責務の一つでしょう。
今回のライブラリ乗り換えや、それに伴うライブラリへのContributeはそうした取り組みの一つであると思っています。

今回、自分はたまたまCodeceptJSに出会い、それが最良の選択肢であると信じ採用しましたが、少なくとも自分が見た範囲で「CodeceptJSが最高のE2Eテストフレームワークである」と結論付けている記事は見つかりませんでした。
もし自分が、自分の手でいろいろなライブラリを試すということをしなければ、CodeceptJSという選択肢には絶対にたどり着かなかったでしょう。

ソフトウェアの世界が日進月歩であるのと同様、ソフトウェアテストの世界もまた目まぐるしく進歩し続けています。
例えば、先日のSeleniumConfTokyoにおいて、Selenium4の主要な機能がいくつか発表されましたが、その中には、CodeceptJSが既に実現しているアイディアもあれば、「その手があったか!」と唸ってしまうような素晴らしいアイディアもありました。
Seleniumもまた、使いやすくストレスレスなライブラリに進化していこうとしています。

また、AIをテストに活用しようという動きも活発に出ており、mablTestimなど既にサービスが稼働しているものも存在します。
これらはE2Eテストにありがちな「テストが壊れる」「テストケースの作成に時間がかかる」などの問題に、機械学習のテクノロジーによって立ち向かい、E2Eテストを楽にしようとしてくれています。

世の中には「このライブラリがベスト!」「このフレームワークを使うべき!」と言った論調の記事も多く存在していますが、次の日にはより優れたライブラリが登場したり、昔からあるライブラリがバージョンアップしてより魅力的になることもあるかもしれません。
行く先々、その時々で最適な選択肢を採ることが出来るよう、日頃からいろいろなライブラリやフレームワークに触れておくようにしたいですね。

…………というわけですので、CodeceptJS、食わず嫌いせず一度触ってみてください。最高なので

おまけ

Qiita Jobsでエンジニア募集してます 。来てね。