Turnip + Sinatra + kintone でメンテナブルなE2Eテストを書こう

こんにちは。大阪開発部の岡田(@y_okady)です。大阪のランチは今日も安くて旨いですね。

サイボウズと言えばSeleniumを使ったE2Eテストのイメージが定着しつつありますが、そのE2Eテストをさらに強固なものにするために新しい仕組み作りにチャレンジしました。

作り始めて3日しか経っていないプロトタイプの段階ですが、構成はだいたい固まってそれなりに動くものができたのでご紹介します。

f:id:cybozuinsideout:20160107191720p:plain

きっかけ

2週間ほど前に読んだ「エッセンシャルスクラム」に、こんなことが書いてありました。

テストはスプリント期間よりも長くかかる場合がある。 そうなるのは、開発チームが巨大な手動テストという負債を背負っているからだ。

サイボウズではE2Eテストの自動化が進んでいると言っても、自動化できているのは試験全体のほんの一部です。ほとんどのテストは手動で実施しており、プログラマが1ヶ月かけて開発したものをQAが1ヶ月かけて手動でテストしています。

この巨大な手動テストという負債を無くすことができれば、より良い開発ができるんじゃないか。これはもうやるっきゃない!

要件

プログラマにとってもQAにとってもメンテナブルであること、これが一番の要件です。他にもいろいろあるのですが、ここでは割愛します。

構成要素

Turnip

https://github.com/jnicklas/turnip

Cucumberの後継として期待されているE2Eテストフレームワークです。RSpec内でテストを実行します。 自然言語でシナリオ(テストしたい一連のステップ)を記述します。

QAがステップを組み合わせてシナリオを設計し、プログラマがステップの具体的な処理を実装する、といった役割分担が可能となります。

SitePrism

https://github.com/natritmeyer/site_prism

Seleniumのデザインパターンのひとつであるページオブジェクトパターンを簡単に使えるDSLです。 DRYで再利用しやすいテストを書くことができます。

Turnip Formatter

https://github.com/gongo/turnip_formatter

Turnipの実行結果をきれいに出力してくれるformatterです。美しさは正義です。

YARD::Turnip

https://github.com/takaishi/yard-turnip

Rubyのドキュメント生成ツールYARDが生成するドキュメントに、Turnipのステップを含めるアドオンです。 ソースコードを見なくても実装済みのステップを簡単に確認できます。

Sinatra

http://www.sinatrarb.com/

軽量Webアプリケーションフレームワークです。kintoneからRSpecを呼び出したり、Turnipの実行結果やYARDのドキュメントを閲覧するために使用します。

kintone

https://kintone.cybozu.com/

我らがkintoneです。JavaScriptカスタマイズを利用することで、kintoneに登録されたデータを外部のWebアプリケーションに簡単に送信できます。

プロトタイプができるまで

Gemfile

source "https://rubygems.org"

gem "rspec", "3.3"
gem "turnip", "1.3.1"
gem "site_prism"
gem "turnip_formatter", "0.3.4"
gem "selenium-webdriver"
gem "chromedriver-helper"
gem "yard"
gem "yard-turnip"
gem "sinatra"

Turnip Formatter(0.3.4)がRSpec(3.3)、Turnip(1.3.1)じゃないとちゃんと動かず、ちょっとハマりました。

RSpecとTurnipの設定

RSpecでTurnipを実行するためのオプションと、実行結果をTurnip Formatterで出力するための設定を .rspec に記述します。

-r turnip/rspec
-r turnip_formatter
--format RSpecTurnipFormatter
--out web/public/report.html

spec_helper.rb に、Turnipの各種設定を記述します。今回はとりあえず、ChromeでSeleniumを実行するようにしています。

require "capybara/dsl"
require "capybara/rspec"
require "turnip"
require "turnip/capybara"
require "site_prism"

Dir.glob("spec/pages/**/*page.rb") { |f| load f, true }

Capybara.default_driver = :selenium
Capybara.run_server = false

Capybara.register_driver :selenium do |app|
  Capybara::Selenium::Driver.new(app, browser: :chrome)
end

SinatraアプリからRSpecを実行

SinatraアプリでHTTPリクエストを受け取ってRSpecを実行できるようにします。kintoneからHTTPリクエストを送れるように、HTTPレスポンスヘッダーに Access-Control-Allow-Origin: * を指定しておきます。

require "sinatra"

set :environment, :production

post "/feature" do
  headers \
    "Access-Control-Allow-Origin" => "*"
  File.write("tmp.feature", params[:feature])
  system("rspec tmp.feature")
end

kintoneからSinatraアプリにHTTPリクエストを送信

シナリオを登録するkintoneアプリを作成して、JavaScriptカスタマイズでシナリオをSinatraアプリに送信できるようにします。

サンプルプログラムは割愛しますが、登録したシナリオをTurnipが解釈できるGherkinという形式に変換して、ボタンクリックで jQuery.post でHTTPリクエストを送信し、処理が完了したら実行結果を別タブで開くだけの簡単なプログラムです。

たとえば、kintoneにログインして非公開スペースを作成し、別のユーザーでログインしてそのスペースが見えていないことを確認するシナリオは、次のように記述できます。

Feature: スペース機能
  Scenario: 非公開スペースを作成する
    * user1 / user1 でログインする
    * [Portal] スペースを作成する
      | スペース名      | 非公開 |
      | サンプルスペース | x     |
    * ポータルアイコンをクリックする
    * "サンプルスペース" と表示されている
    * ログアウトする
    * user2 / user2 でログインする
    * [Portal] "すべてのスペース" のスペース一覧を表示する
    * "サンプルスペース" と表示されていない

なお、GherkinではGiven, When, Thenなどのキーワードを利用するのが一般的ですが、真面目に書くのは大変なので今回は * を使っています。

https://github.com/cucumber/gherkin/blob/master/lib/gherkin/i18n.json#L422-L435

ページオブジェクトとステップの実装

SitePrismを使ってページオブジェクトを実装します。Webアプリケーションのひとつの画面がひとつのクラスになります。 そして、Gherkinで記述されたステップの処理を記述します。

次のサンプルプログラムは、ログイン画面のページオブジェクトと、ログインのステップを記述したものです。 ログイン画面にはユーザー名とパスワードの入力欄と、ログインボタンがあります。 ログインのステップでは、指定されたユーザー名とパスワードを入力欄にセットし、ログインボタンをクリックします。

# ログイン画面のページオブジェクト
class LoginPage < SitePrism::Page
  set_url "<kintoneのURL>"
  element :username, "input[name='username']"
  element :password, "input[name='password']"
  element :submit, "input.login-button"
end

# ログイン画面のステップ
step ":username / :password でログインする" do |username, password|
  @page = LoginPage.new
  @page.load
  @page.username.set username
  @page.password.set password
  @page.submit.click
end

プログラマは、開発中にページオブジェクトとステップをメンテナンスします。 これらをしっかりメンテナンスし続けることが、E2Eテストにおけるプログラマの役割です。

実装済みステップのドキュメント生成

シナリオを記述するQAが実装済みステップを確認できるようにするために、YARDを使ってドキュメントを生成します。

$ yard -o web/public/doc --plugin yard-turnip **/*page.rb

できたもの

前述の、kintoneにログインして非公開スペースを作成し、別のユーザーでログインしてそのスペースが見えていないことを確認するシナリオを実行してみました。

まとめ

E2Eテストはメンテナンスが大変で、続けるのが難しいという話をよく耳にします。 サイボウズではみんなが歯を食いしばってメンテナンスを続けていますが、やっぱり大変です。 そんな状況の中で、手動でやってるテストを全部自動化したいなんて言ったら袋叩きにされるでしょう。

まだ間に合うかもと思った方、すぐにメンテナブルなE2Eテストを導入してください。

もう間に合わないと思った方、袋叩きを恐れずに導入を検討してください。

メンテナブルなE2Eテストを導入して、高品質なWebアプリケーションをこれからもずっと開発し続けましょう!