Selenium + Capybara + Turnip で取り組んでること(まだ途中)

このエントリは Selenium/Appium Advent Calendar 2014 の 3 日目のエントリです。

前日は Kazu_cocoa さんの「七転び八起きなAppiumを使ったモバイルテストのたしなみ」でした。

僕自身は Appium を使ったことがないのですが、そろそろスマホアプリのテストも自動化していかないといけないと考えていたところなのでとても参考になりました。

さて、 3 日目の僕はシステムテストの自動化に関して現在取り組んでいることを書こうと思います。Selenium を使っているというだけで、Selenium ならではのノウハウとかではありません。すいません。なので、Selenium の Advent Calendar にふさわしくないかも知れませんがご容赦ください…

自己紹介

すえなみと申します。漢字で書くと末並です。「末波」とよく間違われますが「末並」です。

とても希少な名字なので勉強会等で「すえなみ」と名乗ってる人間がいたら間違いなく僕のことだと思ってください。 オンラインでは a_suenami とか a.suenami とかで活動してます。

Twitter: https://twitter.com/a_suenami

Facebook: https://www.facebook.com/a.suenami

所属は渋谷にあるファクトリアルという会社で、受託開発のお仕事をしています。RubyPHPあたりがメインの開発言語です。 よろしくお願いします。 http://www.fact-real.com/

忙しい人のために結論

こういうリポジトリを作って自分的に理想のシステムテストを書いていっています。タイトルにもある通りまだ途中なのですが、ぜひ皆様のご意見いただきたいと思ってますので @a_suenami にメンションいただくか Issue を立てていただけると泣いて喜びます。 https://github.com/a-suenami/automated_system_test_sample

僕が考えるシステムテスト

システムテストというのがいいのか、E2Eテストというのがいいのか、受け入れテストというのがいいのか、ちょっと呼び方はいつも迷うのですが、とりあえずこのエントリではシステムテストと呼ぶことにします。

僕が考えるシステムテストのあるべき姿は

を満たしていることです。

ドキュメントである

テストとは「実行可能なドキュメント」であるべきです。

テストは仕様であり、要求であり、ユーザシナリオであり、そして、それを実行した結果がグリーンなのであればそれは現時点でのそのシステムの振る舞いです。

テストシナリオを読んで意味が分からないようではそれはよいテストとは言えません。

プログラマが(プログラミング言語でなく)自然言語で書く

エンドツーエンドでシステムをテストする場合、それはプログラマではない人によって書かれるべきです。

DDD でいえばドメインエキスパート、スクラムでいえばプロダクトオーナーになるかと思いますが、いつもシステムがどう振る舞うべきかを最終的に決めるのはプログラマではありません*1。彼らの言葉で記述できないのであればそれは適切に仕様を記述できないリスクが残ってしまいますし、仕様を適切に記述できてなければテストとしても欠陥となります。

プロダクト側のアーキテクチャに依存しない

これは特に最近考えていることですがシステムテストがプロダクトのアーキテクチャに依存するのは非常にリスクです。

日進月歩のこの世界で次々と新しいライブラリやフレームワークミドルウェアが出てきて、その都度最適なアーキテクチャは変わり、プロダクトのライフサイクルはどんどん短期化していきます。そのときにシステム全体の振る舞いは変わっていないことを保証するものこそがシステムテストであり、それが特定のアーキテクチャに依存していたら意味がありません。

取り組んでいることと各ツール紹介

こんなリポジトリを作って、Web アプリケーションにおけるシステムテストの記述を楽にするためにいろいろ模索しています。

https://github.com/a-suenami/automated_system_test_sample

プロダクト側のコードは単なる Rails のアプリケーションで、システムテストは HTTP プロトコルのみに依存します。したがって、プロダクトが Rails 以外で再実装されてもテスト側は影響を受けません。

アーキテクチャとしては

  • Turnip
  • Capybara
  • Selenium WebDriver
  • PhantomJS

という感じで今のところは動いています。

Turnip

ご存知の方も多いと思いますが、Cucumber のように Gherkin 形式でテストシナリオを記述するための Ruby 製ライブラリです。Cucumber は Ruby 以外の言語へも移植されていますが、Ruby でテストシナリオを記述する場合は最近では Turnip のほうがよく見かける気がします。

Capybara

こちらもご存じの方が多いと思いますが、ブラウザ操作を抽象化して DSL として提供するライブラリです。

実際のブラウザ操作を行うドライバーは差し替えることができるようになっていて、デフォルトは rack_test というドライバーで rack に依存しますが、ここを selenium ドライバーにすれば Capybara が提供する DSL を用いて Selenium を利用することができるようになります。

PhantomJS

WebKit ベースのヘッドレスブラウザで、JS の実行も可能です。

JavaScriptAPI を提供しており、それを用いて自由にブラウザを操作することができるためシステムテストの自動化によく利用されます。

取り組みの狙い

システムテストのレイヤー化

システムテストには以下のようなレイヤーがあると考えています。

  • シナリオレイヤー
    • 用意されたステップを組み合わせてテストシナリオを構成する。
    • ドメインエキスパートやプロダクトオーナーが理解可能な言語で記述する。
  • ドメイン特化ステップレイヤー
    • 汎用ステップを組み合わせて対象ドメインに特化した新たなステップを構成する。
  • 汎用ステップレイヤー
    • シナリオレイヤーやドメイン特化ステップレイヤーで利用する各ステップを定義する。
    • プラグラムを書く必要があるため、プログラマの作業領域になるが保守性を向上させるために DSL で定義されるとよい。
  • ドライバーレイヤー
    • 実際にブラウザを操作する。
    • ブラウザごとにドライバーが必要になる。

このレイヤー構造は僕の考えではありますが、継続デリバリーを参考にはしています*2

継続的デリバリー 信頼できるソフトウェアリリースのためのビルド・テスト・デプロイメントの自動化

継続的デリバリー 信頼できるソフトウェアリリースのためのビルド・テスト・デプロイメントの自動化

汎用ステップの充実

Selenium やその他のシステムテスト自動化ツールを使って Web アプリケーションのシステムテストを自動化していると「<フォーム>に<文字列>を入力する」や「<リンク>をクリックする」というのはたくさん出てきます。

こういったキーワードで駆動しやすいテストスクリプトは汎用ステップとしてシステムを横断し、その組み合わせで各システムのドメイン特化ステップやシナリオを作成してもらえると非プログラマでもテストを作成しやすくなるかと思っています。

何故 Selenium を選んだのか?

システムテストをこのようにレイヤー化すると Selenium というツールはドライバーレイヤーのツールとして実に優れていると思っていて、メジャーなブラウザであれば大抵ドライバーが提供されているので設定を書き換えるだけで容易にクロスブラウザのテストを実現できます。

また、Selenium Grid を用いればクロスブラウザテストを行って実行速度が問題になった場合でもスケールアウトが容易です。

Turnip + Capybara という構成でテストシナリオを書く場合、その後ろには Poltergeist という PhantomJS のドライバーを使うケースもあるのですが、PhantomJS だけでなく他のブラウザでも同じシナリオのテストをするためには Selenium がベストな選択肢でした。

今後取り組みたいと思っていること

くどいですがこの取り組みはまだ途中でやりたいことはいろいろあります。

  • 汎用ステップの充実(上述の通り)
  • Gem 化
  • 多言語化
  • プログラマがテストシナリオを作成しやすくするための Web UI の作成
  • HTML 中の要素を特定するためのマークアップパターンの提案
    • テンプレートエンジンの実装も視野に入れて

今後ともがんばっていきたいと思っているので、ぜひご意見等いただければ嬉しく思います。

バトンタッチ

次は hiroshitoda-prospire さんです。よろしくお願いします。

*1:もちろんプログラミングのできるドメインエキスパートやプロダクトオーナーがいてもいいですが…

*2:継続的デリバリーでは「受け入れ基準」「実装レイヤ」「アプリケーションドライバレイヤ」の3つにレイヤー分けされています。