この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
丹内です。
掲題の通り、受け入れ条件を意味のあるものにするため、開発者が書く自動テストを意味のあるものにする話をします。
受け入れ条件とは
受け入れ条件とは、スプリントの計画時に決める「これを満たせば達成とする」という条件のことです。
チームやスクラムの運用によって内容は変わると思うのですが、私は「期待される成果物や機能を具体的に書くこと」が、受け入れ条件にとって必要なことだと考えています。
この受け入れ条件は、例えば「ユーザはタグを付けてブログを投稿できる」というストーリーの場合、以下のようになります。
このうち上の2つは具体的な機能に関する受け入れ条件ですが、これと対応するように自動テストを書きたいと思います。
Turnipとは/受け入れ条件との関連
Turnipとは、RSpec向けのGherkin拡張(Gherkin extension for RSpec)です。
Cucumberの親戚なのですが、Cucumberより簡単に記述することができると言われています(私はCucumberを使ったことが無いです。スミマセン)。
Turnipの詳細は以下のリンクが詳しいので、そちらを御覧ください。
エンドツーエンドテストの自動化は Cucumber から Turnip へ - RubyMagazine
Turnipでは具体的な操作(フォームに入力する、ボタンをクリックするなど)に日本語や英語の名前をつけて、それを別のファイルでテストとして記述することで、自動テストを実現しています。
直接specファイルに書いても同じことができますが、Turnipで書くテストをスクラムの受け入れ条件と一致させることで、
というメリットがあります。
例えば、以下の様なRSpecがあったとします。
describe 'ブログ投稿' do
let(:title) { 'ブログタイトル' }
let(:entry) { 'ブログ本文' }
before { visit root_path }
context 'ブログを投稿したとき' do
subject { page }
before do
click_link I18n.t('navbar.new')
fill_in 'blog[title]', with: title
fill_in 'blog[entry]', with: entry
click_button I18n.t('helpers.submit.create')
end
it 'ブログが表示されること' do
expect(subject).to have_content title
expect(subject).to have_content entry
end
end
end
これをTurnipで書く場合、まずstepを定義します。
steps_for :blog do
step 'rootに移動する' do
visit root_path
end
step 'ナビゲーションバーから投稿画面に移動する' do
click_link I18n.t('navbar.new')
end
step 'タイトルとエントリを入力して投稿ボタンを押す' do
fill_in 'blog[title]', with: 'test title'
fill_in 'blog[entry]', with: 'test entry'
click_button I18n.t('helpers.submit.create')
end
step 'ブログが表示されること' do
expect(page).to have_content 'test title'
expect(page).to have_content 'test entry'
end
end
次に、featureファイルにテストを書きます。
Feature: ブログの投稿
@blog
Scenario: ユーザはブログを投稿できる
When rootに移動する
And ナビゲーションバーから投稿画面に移動する
And タイトルとエントリを入力して投稿ボタンを押す
Then ブログが表示されること
このfeatureファイルの内容を、ストーリーの受け入れ条件と一致させてやることで、仕様と実装を一致させやすくなります。
ことスクラムにおいては、
という点で、非開発者にもリーダブルなテストを書くことが重要だと思います。
また、逆に、非開発者でもテストやテストレポートを読むことで、開発者との距離を縮めることができます。
Turnipのセットアップ
今回はRailsの場合を想定します。
$ rbenv version
2.2.2 (set by /Users/tannaiyuki/.rbenv/version)
$ rbenv exec rails -v
Rails 4.2.2
$ rbenv exec rails new TurnipSample --test=rspec --database=mysql --skip-bundle
...
ここから、Turnipというgemを使えるようにセットアップします。
まずはGemfileを修正しましょう。以下を追記して、bundle installをします。
group :test do
gem 'rspec-rails'
gem 'capybara'
gem 'database_rewinder'
gem 'turnip'
end
次に、RSpec/Capybara/Turnipを使うための設定を行います。
各種設定
RSpecをインストールします。
$ bundle exec rails g rspec:install
rspecの設定をします。
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV['RAILS_ENV'] ||= 'test'
require 'spec_helper'
require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'
# 以下の設定を追加する
require 'capybara/rails'
require 'turnip'
require 'turnip/rspec'
require 'turnip/capybara'
Dir.glob('spec/steps/**/*steps.rb') { |f| load f, true }
...
# 以下も追加
RSpec.configure do |config|
# database_rewinder gem
config.before :suite do
DatabaseRewinder.clean_all
end
config.after :each do
DatabaseRewinder.clean
end
end
Turnipはspec/turnip_helper.rbの設定を読んで動作するので、このファイルを追加します。ここではrails_helperの設定を引き継ぐようにします。
require 'rails_helper'
そして、rspecコマンドでturnipも実行されるように、.rspecに追記をします(無ければ作成です)。
--require spec_helper
-r turnip/rspec
動作確認してみます。
$ bundle exec rake db:create
$ bundle exec rspec
No examples found.
Finished in 0.00038 seconds (files took 0.57307 seconds to load)
0 examples, 0 failures
正常終了したら準備完了です。
Scaffoldしてテストを書いてみる
$ bundle exec rails g scaffold blog title:string entry:text
invoke active_record
create db/migrate/20150707084213_create_blogs.rb
create app/models/blog.rb
invoke test_unit
create test/models/blog_test.rb
create test/fixtures/blogs.yml
(以下、省略)
$ bundle exec rake db:migrate
== 20150707084213 CreateBlogs: migrating ======================================
-- create_table(:blogs)
-> 0.0291s
== 20150707084213 CreateBlogs: migrated (0.0291s) =============================
rootも設定します。
Rails.application.routes.draw do
resources :blogs
root 'blogs#index' <- 手で追記する
rails serverでscaffoldの画面が見れればOKです。
いよいよテストを書きます。
今回は、受け入れ条件を以下のように決めます。これは本来、スクラムイベントによって決定されるものです。
最初に、受け入れ条件をfeatureに書き写します。 spec/features/blog.feature です。
Feature: ユーザはブログ記事を作成できる
@blog
Scenario: タイトルと本文を入力して記事を作成できること
When トップを訪問する
And Newリンクをクリックする
And タイトルと本文を入力して作成ボタンを押す
Then 作成したタイトルが表示されていること
And 作成した本文が表示されていること
この時点でテストを実行しても、stepを実装していないのでpendingされます。以下のように出力されます。
$ bundle exec rspec
*
Pending: (Failures listed here are expected and do not affect your suite's status)
1) ユーザはブログ記事を作成できる タイトルと本文を入力して記事を作成できること When トップを訪問する -> And Newリンクをクリックする -> And タイトルと本文を入力して作成ボタンを押す -> Then 作成したタイトルが表示されていること -> And 作成した本文が表示されていること
# No such step: 'トップを訪問する'
# ./spec/features/blog.feature:4
Finished in 0.01698 seconds (files took 5.62 seconds to load)
1 example, 0 failures, 1 pending
では、stepを実装しましょう。ファイル名は spec/steps/blog_steps.rb です。
steps_for :blog do
step 'トップを訪問する' do
visit root_path
end
step 'Newリンクをクリックする' do
click_link 'New Blog'
end
step 'タイトルと本文を入力して作成ボタンを押す' do
fill_in 'blog[title]', with: 'test title'
fill_in 'blog[entry]', with: 'test entry'
click_button 'Create Blog'
end
step '作成したタイトルが表示されていること' do
expect(page).to have_content 'test title'
end
step '作成した本文が表示されていること' do
expect(page).to have_content 'test entry'
end
end
テストを実行すると、以下のようにPASSすることがわかります。
$ bundle exec rspec -fd
ユーザはブログ記事を作成できる
タイトルと本文を入力して記事を作成できること
When トップを訪問する -> And Newリンクをクリックする -> And タイトルと本文を入力して作成ボタンを押す -> Then 作成したタイトルが表示されていること -> And 作成した本文が表示されていること
Finished in 0.22194 seconds (files took 2.05 seconds to load)
1 example, 0 failures
Turnipの使いドコロ
TurnipやRSpec/Capybaraを使ったこのような自動テストは、以下の様なデメリットがあります。
ひたすらにこのようなテストを書いていくと、いずれテストの実行時間やメンテにかかるコストが大きくなってしまいます。
何を以って「意味のあるテスト」であるかは人によって定義が変わる中でその指標としてスクラムノ受け入れ条件を活用すると良いと思います。
受け入れ条件以外の項目はユニットテストとしてガンガン書いて、開発を加速させましょう。
まとめ
スクラムの受け入れ条件と整合性のとれる自動テストを記述する観点を記述しました。
価値を定義しながら実装すると、書き換えやコンフリクトのためテストの必要性が大きくなります。
素早く意味のあるテストを書いて開発を加速させ、実現できる価値を最大化しましょう。