Rails4: fields_forとstrong_parametersで複数レコードの更新に対応する方法

railsって1つのレコードを更新するのには特に困らないんですが、複数レコード更新したい場合に結構困ります。リスト表示してフラグにチェックいれて更新とか、そういうやつです。似たようなことをやっていたところのコードをコピーして作ろうとしたんですが、strong_parametersのところでエラーが発生。うまくいかず、悩んでいたら、ゆうじさんに教えてもらえました。

なるほど。おそらくこれだっ!と思い、やってみたところ、うまくいったぽいので、メモで残しておきます。

View

ビュー側では、form_tag内でfields_forを使って複数のモデルのデータ用フォームを作るのですが、第一引数にhoges[]を渡すことで、複数のデータ用にしておきます。

- model_class = Hoge
= form_tag update_enable_list_hoges_path, method: :put do
  table
    thead
      tr
        th = model_class.human_attribute_name(:id)
        th = model_class.human_attribute_name(:name)
        th 有効フラグ
    tbody
      - @hoges.each do |hoge|
        = fields_for('hoges[]', hoge, index: nil) do |fh|
          tr
            td = hoge.id
            td = hoge.name
            td
              = fh.check_box :enable
              = fh.hidden_field :id
  .form-actions
    = submit_tag '更新'

Controller

コントローラー側では、fields_forの第一引数で定義したhoges[]に対応するようにstrong_parametersを定義します。

class HogesController < ApplicationController
  # 省略

  private
  def hoge_params
    # 通常のcreate, updateで呼ばれるほう
  end

  def hoges_params
    params.require(:hoges).map do |param|
      ActionController::Parameters.new(param.to_hash).permit(:id, :enable)
    end
  end
end

これで、複数レコード用のフォームに対応することができました。
ゆうじさん、ありがとう!!


私がインテグレーションテストを書く際のやり方について

前回、私がRSpecを書く場合のやり方についての記事を書きました。
今回はインテグレーションテストについて書きます。

私の場合はController,Viewのテストは基本的には書かず、インテグレーションテストで済ませています。インテグレーションテストフレームワークは、

  • Cucumber
  • Turnip
  • RequestSpec
  • FeatureSpec
  • Steak

など色々あります。以前はCucumberを使っていたのですが、現在担当しているプロジェクトを始める前に、Cucumberに不満があったので、他のやつを試そうという話になって、Steak, RequestSpec, FeatureSpecを試して、FeatureSpecにしました。Turnipはこれらを検討していた頃は有名でなかったので、試していませんが、うちの会社の他のプロジェクトでは、Cucumberの代わりにTurnipを使うようになっています。

ちなみになんでCucumberをやめたかというと、Cucumberは日本語が書けるけれど、別にプログラマがテストできていればいいということであれば、日本語でテスト内容を書くのは二度手間ではないか?という点があったからです。仕様についてはit(scenario)に日本語で書けるし、ブラウザの操作部分を日本語で書く旨味ってなんだろうか?ということになったときに、『自分たちだけが確認すればいい状況だったらメリットはそこまでないよね』ということで、私の担当しているプロジェクトではFeatureSpecを使ってます。

私がインテグレーションテストを行う場合の環境について

私が書く場合の話なので、ここでもFeatureSpecについて書きます。

ちなみにFeatureSpecは以下のものを使って環境を作ってます。

  • RSpec
  • FactoryGirl
  • Capybara(ヘッドレスブラウザ)
  • PhantomJS(ヘッドレスなJavaScriptが使える環境)
  • Poltergeist(仮想ブラウザでJavaScriptを操作)
  • DatabaseRewinder(データベースの掃除を行う)

Seleniumなど、実際のブラウザを使うようなテストにすると速度が遅くなりすぎるので、ヘッドレスなブラウザを使います。これらを使うためのspec_helper.rbの設定の書き方は、ここでは省略します。リクエストがあったら公開を考えます。

FeatureSpecはRSpecなので、書き方は基本的に普通のRSpecと同じですが、インテグレーションテスト用の書き方が準備されています。

feature
describeと同じ
background
beforeと同じ
scenario
itと同じ

インテグレーションテストの場合はシナリオ単位でテストするから、scenarioというDSLはしっくりきます。

テストをするときに意識するポイント

インテグレーションテストは人間の手でやると無茶苦茶時間がかかるところなので、私はできるだけ多くのパターンを書いておきます。が、モデルのテストと重複するようなものは省略します。実際にユーザーがやりそうな操作を想像して、正常系から徐々に異常系のテストも足していってテストを育てていきましょう。

スローテストになりやすいので注意

ただし、インテグレーションテストはブラウザを経由するため、動作はやはり遅くなります。数秒から十数秒で終わるModelのテストに比べると、かなり違います。データの投入はなるべく最小限、かつ、JavaScriptを使うところと使わないところの見極めが大事になります。JavaScriptを必要としないページの場合はなるべくJavaScriptを使う設定をOnにしないようにします。JavaScriptが使えるようにすると、途端にテストのスピードが落ちるからです。

ログイン処理のテストシナリオを書いてみる

では、ログインを例としてテストを書いてみましょう。

feature 'ログイン処理' do
  background do
    visit new_user_session_path
  end
  context '登録済みユーザーでログインを試みた場合' do
    scenario 'ログインできること'
  end
  context '間違ったパスワードを入力した場合' do
    scenario 'エラーメッセージが表示されること'
  end
end

とりあえず思いついたテストは2つとします。

成功のパターン

まず、ログインできること、のテストを記述します。

backgroundブロックで、ログイン用のユーザーを作っておきました。

context '登録済みユーザーでログインを試みた場合' do
  background do
    FactoryGirl.create :user
  end
  scenario 'ログインできること' do
    fill_in 'user_email',    with: 'patorash@email.com'
    fill_in 'user_password', with: 'password'
    click_button 'ログイン'
    expect(page).to have_content 'ログインしました'
  end
  # 略
end

ログインに成功したときにだけある文章を見つけたら、ログインに成功したこととします。

失敗のパターン

次に、ログインに失敗したときのパターンです。
今度はbackgroundブロックを作っていません。
これはユーザーを保存するとデータベースへのアクセスが発生してテストが遅くなるからです。なるべくDBにデータを入れないように注意しておくと、スローテストになりにくいと思います。

context '間違ったパスワードを入力した場合' do
  scenario 'ログインできないこと'
    fill_in 'user_email',    with: 'patorash@email.com'
    fill_in 'user_password', with: 'password'
    click_button 'ログイン'
    expect(page).to have_no_content 'ログインしました'
    expect(page).to have_content 'メールアドレスかパスワードが違います'
  end
end

ログインしましたというメッセージがないことと、エラーメッセージがでていることを確認しています。

ログイン後の機能をテストする

ログイン後の機能をテストしてみたいと思います。
まずはすごくベタに書いてみます。テストする内容は「ブログの記事一覧を表示」とかにしてみましょう。

feature 'ブログの記事について' do
  background do
    FactoryGirl.create :user
    visit new_user_session_path
    fill_in 'user_email', with: 'patorash@email.com'
    fill_in 'user_password', with: 'password'
    click_button 'ログイン'
    visit articles_path
  end
  context '記事が既に存在する場合' do
    scenario '記事のタイトル一覧が表示されること'
  end
  context '記事が無い場合' do
    scenario 'まだ記事がありませんと表示されること'
  end
end

シナリオはこんな感じだと思います。このシナリオのテストは、適当に書いてもらうとしましょう。ここで何に言及したいかというと、backgroundブロックに書いたログイン処理です。この処理、さっきのテストでも書いていますね?こういうブラウザ上での処理を毎回書いていると、とても大変です。なので、関数にしてしまいましょう。

ログイン処理をテストのスコープ内で関数化する

feature 'ブログの記事について' do

  def create_user_and_login
    FactoryGirl.create :user
    visit new_user_session_path
    fill_in 'user_email', with: 'patorash@email.com'
    fill_in 'user_password', with: 'password'
    click_button 'ログイン'
  end

  background do
    create_user_and_login
    visit articles_path
  end
  # 略
end

関数になったことで、背景でなにをしているのかがわかりやすくなりました。「ユーザー作ってからログインしてるんか、ふむふむ…」という感じですね。このテスト内容のみで使いそうな関数ならば、このfeatureファイル内だけで関数を定義すればいいと思いますが、他のfeatureでも同じような処理を使い回しそうだなと思ったら(とくにこのログイン処理のようなもの)、関数を外部に定義しておきましょう。

feature用の関数を定義する

私の場合、spec/support以下に、テスト用のヘルパーを作っています。用途によっていろいろなヘルパーを作りますが、今回はfeature_helpers.rbを作ってみます。

module FeatureHelpers
  def create_user_and_login
    FactoryGirl.create :user
    visit new_user_session_path
    fill_in 'user_email', with: 'patorash@email.com'
    fill_in 'user_password', with: 'password'
    click_button 'ログイン'
  end
end

このヘルパーはfeatureのテストからだけ呼び出せるように、spec_helperで定義しておきます。

RSpec.configure do |config|
  config.include FeatureHelpers, type: :feature
end

こうすることで、spec/features/ならどこからでもログイン用関数が呼べるようになりました。

feature 'ブログの記事について' do
  background do
    create_user_and_login
    visit articles_path
  end
  # 略
end

Ajaxのテストを行う(JavaScriptを含むテスト)

先ほどのブログの記事のタイトル一覧の表示がAjaxで取得されているとします。ブラウザがJavaScriptを使えない場合、テストが成功しないため、Ajaxのテストを行いたい場合は、テスト内でJavaScriptを有効にします。
JavaScriptを有効にするには、js: trueを設定します。

feature 'ブログの記事について' do
  background do
    create_user_and_login
    visit articles_path
  end
  context '記事が既に存在する場合' do
    scenario '記事のタイトル一覧が表示されること', js: true do
      expect(page).to have_content '初めての投稿です。'
    end
  end
  # 略
end

ちなみにこのjs: trueですが、このテスト内の全てでJavaScriptを使う場合は、以下のようにもできます。

feature 'ブログの記事について', js: true do
  background do
    create_user_and_login
    visit articles_path
  end
  context '記事が既に存在する場合' do
    scenario '記事のタイトル一覧が表示されること' do
      expect(page).to have_content '初めての投稿です。'
    end
  end
  # 略
end

ちなみにdescribe, context毎にも設定可能です。シナリオによってはJavaScriptが要らない場合、できればシナリオ単位で有効にしましょう。JavaScriptをオフにしているテストは高速になりますから、シナリオ単位でJavaScriptを有効にしたほうがテストの速度に違いが出てきます。

入力項目がちょっと違うテストを簡単にする

画面系のテストはなにしろ入力のテストが面倒です。たとえば、ユーザー作成画面で色んなパターンを入力させてみたとしましょう。

feature 'ユーザー管理' do
  background do
    create_user_and_login
    visit users_path
  end
  context 'ユーザー登録で' do
    background do
      click_link '新規作成'
    end
    scenario 'ユーザーを登録できること' do
      fill_in 'user_name', with: 'サンプルユーザー'
      fill_in 'user_email', with: 'sample@email.com'
      fill_in 'user_password', with: 'password'
      fill_in 'user_password_confirmation', with: 'password'
      click_button '登録'
      expect(page).to have_content '登録しました'
    end
    scenario '登録済みのメールアドレスはエラーになること' do
      fill_in 'user_name', with: 'サンプルユーザー'
      fill_in 'user_email', with: 'patorash@email.com' # 登録済みメールアドレス
      fill_in 'user_password', with: 'password'
      fill_in 'user_password_confirmation', with: 'password'
      click_button '登録'
      expect(page).to have_content '既に登録済みです'
    end
    scenario 'パスワードが短すぎるとエラーになること' do
      fill_in 'user_name', with: 'サンプルユーザー'
      fill_in 'user_email', with: 'sample@email.com'
      fill_in 'user_password', with: 'p'
      fill_in 'user_password_confirmation', with: 'p'
      click_button '登録'
      expect(page).to have_content 'パスワードは8文字以上にしてください'
    end
  end
end

同じ事を何度も書いていてすごく…冗長です。でもまぁコピペでできるからよくない?と思うかもしれませんが、入力項目が後で追加されると全部修正していかなくてはなりません。それは面倒です。ですので、これもまとめておきましょう。ブロックを使います。FeatureHelpersに新しいメソッドを追加しましょう。

module FeatureHelpers
  # 略
  def input_and_submit_user_info
      fill_in 'user_name', with: 'サンプルユーザー'
      fill_in 'user_email', with: 'sample@email.com'
      fill_in 'user_password', with: 'password'
      fill_in 'user_password_confirmation', with: 'password'
      yield if block_given?
      click_button '登録'
  end
end

これを使って書き直してみます。

feature 'ユーザー管理' do
  background do
    create_user_and_login
    visit users_path
  end
  context 'ユーザー登録で' do
    background do
      click_link '新規作成'
    end
    scenario 'ユーザーを登録できること' do
      input_and_submit_user_info
      expect(page).to have_content '登録しました'
    end
    scenario '登録済みのメールアドレスはエラーになること' do
      input_and_submit_user_info do
        fill_in 'user_email', with: 'patorash@email.com' # 登録済みメールアドレス
      end
      expect(page).to have_content '既に登録済みです'
    end
    scenario 'パスワードが短すぎるとエラーになること' do
      input_and_submit_user_info do
        fill_in 'user_password', with: 'p'
        fill_in 'user_password_confirmation', with: 'p'
      end
      expect(page).to have_content 'パスワードは8文字以上にしてください'
    end
  end
end

登録ボタンを押す前に、入力項目を上書きするチャンスをブロックで与えています。こうすることで、フォーム入力を一部書き換えたい場合など、柔軟に対応できるようになりました。

テストもわかりやすさが重要!

とりあえず、以上が私がインテグレーションテストをする際に意識している点です。実践的な内容だと他にもあると思いますが、前回と今回で何が言いたかったかというと、『テストもわかりやすく、速く動くように書こう』ということです。何をテストしているのかわかりにくくなってしまうと、テスト自身が負債になってしまう可能性があります。まぁ、私の書き方がわかりにくいんじゃないか?という人もいるとは思います。これが正解でもありませんので、もっといい書き方などがあれば教えてください。


私がRSpecを書く場合のやり方について

今日は木曜日だったので、ハンバーグの会(Okayama.rb)に参加してきました。

今日は@mako_wisにテストの書き方について相談を受けたので、粒度とかについて説明しましたが、私は説明し始めると早口になってしまうので詰め込みすぎたかもしれないと思ったのでちょっとまとめておこうと思いました。ちなみに、書き方といってもRSpecの始め方とかではないです。その点はあしからず。

Railsプロジェクトのいいところは、テストがとてもしやすいところだと思います。
私は今の会社に入るまで、テストは書きたいけれど、どう書けばいいのかわからなかったのと、頑張って書いてみたものの、成果が周りに評価されなかったのでこのままでいいのだろうか?と思い悩んでいました。しかし、既にテストがあるプロジェクトに入って書き方を学べた事と、同僚とThe RSpec Book読書会を社内で開いて勉強したおかげで、結構綺麗に書けるようになったんじゃないかな?と思ってます。持つべき物はいい仲間と向上心です(キリッ

テスト戦略

まずテスト戦略についてです。
基本的にはテストは『開発者の不安を取り除くため』にあります。裏を返せば、不安でないところはテストを書く必要はありません。テストはやりすぎるとスピードが遅くなるし、ライブラリのテストをやっているようなことになってきます。ありそうなのは、Deviseを使ってユーザー認証作ってて、Deviseのテストになってしまっていたりとか。軽くならいいでしょうが、やりすぎはやめましょう。

どこまでテストするか?ですが、不安なところはテストしますが、重複しそうなところはやりません。以下のようなものをテストします。

  • Model(ビジネスロジックが詰まってる)
  • ActiveDecorator(Modelの延長なので)
  • rake task(定期処理が多いから)
  • インテグレーションテスト(Capybara + PhantomJS + poltergeist)

インテグレーションテストをするので、基本的にControllerとViewのテストはそちらで賄います。ただし、絶対にテストしないわけではありません。不安ならテストします。

どこからテストするのか?Modelでしょ!

Railsでテストが一番書きやすいのは、Modelです。どうしてテストしやすいかというと、

  • テスト対象が明確である
  • 依存関係が少ないので取りかかりやすい
  • 高速である

という点です。まずはモデルのテストから書いて、慣れていく事をオススメします。

テストを定義してみる

では実際にRSpecでモデルのテストを書いてみるとしましょう。私はテストさえ書けば、テストを先に書こうが後に書こうがどっちでもいいと思います。どちらにしても最初に想定してないパターンとか思いつくので。でもモデルはテスト駆動しやすいと思うので、先に設計のつもりで書いたりすることが多いです。

たとえば、Userモデルがあって、名前、メールアドレス、パスワードを持っていて、登録するときには確認用パスワードが必須とします。
そうなると、こういう感じでテストが書けるかと思います。

require 'spec_helper'

describe User do
  context '正しいデータを入れた場合' do
    it "登録できること"
  end
  context '既にデータがある場合' do
    it '編集できること'
    it '削除できること'
  end
  describe 'エラーチェック' do
    context '何も入れない場合' do
      it 'エラーになること'
    end
    context 'メールアドレスでない場合' do
      it 'エラーになること'
    end
    context 'パスワードと確認用パスワードが一致しない場合' do
      it 'エラーになること'
    end
  end
end

とりあえず考えられるパターンを先に書き出しておきます。
it文だけ書いておくとpending状態になるので、思いつくがままに書いていると、仕様がテストに書き出されてきます。ちなみにdescribeとcontextはどちらでもいいのですが、contextを使う事で、テスト対象がどういう場合かを定義しておきます。describeはいくらでもネスト(入れ子)できますが、最後はcontextにするという感じです。よく使うパターンだと、

  1. describe・・・〜で、〜であり
  2. context・・・〜の場合
  3. it・・・〜であること、〜でないこと

で終わります。

テストを実装してみる

では、次に、正しいデータを入れた場合、のテストを実装してみましょう。

context '正しいデータを入れた場合' do
  it "登録できること" do
    user = User.new(name: 'パトラッシュ',
                    email: 'patorash@email.com',
                    password: 'password',
                    password_confirmation: 'password')
    expect(user.save).to be_true
  end
end

普通に書くと、上のようになります。しかし、これから次は編集、削除のテストもあるのに、毎回User.newやUser.createを書くのは面倒です。そこで、テスト用データを作成しておきます。

FactoryGirlを使おう!

テスト用データの作成ですが、私はFactoryGirlを使って作っています。FactoryGirlは多機能なので色々できますが、まずはシンプルに使いましょう。

FactoryGirl.define do
  factory :user do |d|
    d.name 'パトラッシュ'
    d.email 'patorash@email.com'
    d.password 'password'
    d.password_confirmation 'password'
  end
  
  factory :user_invalid_password, parent: :user do |d|
    d.password 'invalid_password'
    d.password_confirmation 'wrong_password'
  end
end

親データを指定して、一部の値を書き換えたりすることができるので、想定できるパターンのユーザーを都度作成しておくと、どういう意図のテストデータかわかりやすくなります。例えばuser_invalid_passwordは、パスワードが違うテストデータという事になります。

FactoryGirlを使ってテストを直してみる

では、FactoryGirlを使って直してみます。FactoryGirl.buildを使うと、モデルにデータを入れて作成した状態(newした状態)になります(DBに保存はされていません)

context '正しいデータを入れた場合' do
  it "登録できること" do
    user = FactoryGirl.build(:user)
    expect(user.save).to be_true
  end
end

とてもシンプルになりました。

テスト対象を明確にしよう

さっきの修正でも十分機能はしていますが、さらに上を目指しましょう。何をテストしているのかを明確にするべきです。RSpecでは、テスト対象のオブジェクトを、subjectに設定することができます。こうすると、テスト内でsubjectと書くと、対象のオブジェクトにアクセスできます。

context '正しいデータを入れた場合' do
  subject { FactoryGirl.build(:user) }
  it "登録できること" do
    expect(subject.save).to be_true
  end
end

なんか、逆に長くない?と思われたかもしれません。そういうこともあります。ありますが、subjectがテスト対象であるというのが明確になりました。また、subjectに設定すると、英語圏の人はテストが書きやすくなります。日本語圏の我々は上の書き方でいいですが、一応紹介しておきます。

context '正しいデータを入れた場合' do
  subject { FactoryGirl.build(:user) }
  its(:save) { should be_true }
end

好きな方で書きましょう。私はどっちも使います。

次のテストをやってみよう!

次のテストは編集と削除です。事前にデータを登録することになりますが、これもFactoryGirlを使えば簡単です。FactoryGirl.createを使うと、対象のモデルをDBに保存までしてくれます。

context '既にデータがある場合' do
  before do
    @user = FactoryGirl.create(:user)
  end
  it '編集できること' do
    @user.name = "ネロ"
    expect(@user.save).to be_true
  end
  it '削除できること' do
    expect(@user.destroy).to be_true
  end
end

beforeを使えばテストの事前処理が、afterを使うとテストの後処理が定義できます。beforeは、:each, :allがあります(:aroundもあった気がする…)。省略すると、:eachになります。:eachは、テストのitが実行される毎に実行されます。:allは、そのフォーカスの中で1度だけ実行されます。基本的には、:eachを使います。:allはRSpecの事前定義とかで使います。

さて、勘のいい人は気付いたかもしれませんが、これ、subjectを使うともっと短くできます。

context '既にデータがある場合' do
  subject { FactoryGirl.create(:user) }
  it '編集できること' do
    subject.name = "ネロ"
    expect(subject.save).to be_true
  end
  it '削除できること' do
    expect(subject.destroy).to be_true
  end
end

テストシナリオを増やす

ここで、ふと気付きました。メールアドレスで認証しようと思っているのに、メールアドレスがユニークかどうかチェックしてないな、と。気付いた時点でとりあえずテストケースを書きましょう。

describe 'エラーチェック' do
  context '何も入れない場合' do
    it 'エラーになること'
  end
  context 'メールアドレスでない場合' do
    it 'エラーになること'
  end
  context 'パスワードと確認用パスワードが一致しない場合' do
    it 'エラーになること'
  end
  context 'メールアドレスが重複する場合' do
    it 'エラーになること'
  end
end

あとで追加しようと思うと、ど忘れしてしまうことが多いので、とりあえず書いて残しておきます。別にすぐテストの実装を書く必要はありません。

エラー系のテストを書く

では、エラー系を書いていきます。正常系が登録できなければならないのは当然ですが、イレギュラーなことをテストで書いておくと、すごく安心します。新しい機能を追加した結果、既存の機能に影響することはよくありますから、思いつくパターンはなるべく書きます。

Userモデルのバリデーションですが、

  • 名前、メールアドレス、パスワードは必須
  • メールアドレスは形式をチェック
  • パスワードはパスワード確認と一致するかチェック

がされているものとします。

まずは、何も入れない場合のテスト。

describe 'エラーチェック' do
  context '何も入れない場合' do
    subject { User.new }
    it 'エラーになること' do
      expect(subject).not_to be_valid
      expect(subject).to have(1).errors_on(:name)
      expect(subject).to have(2).errors_on(:email)
      expect(subject).to have(2).errors_on(:password)
    end
  end
  context 'メールアドレスでない場合' do
    it 'エラーになること'
  end
  context 'パスワードと確認用パスワードが一致しない場合' do
    it 'エラーになること'
  end
  context 'メールアドレスが重複する場合' do
    it 'エラーになること'
  end
end

expect(subject).not_to が初めて出てきました。not_toは、〜でないことを確認します。ここでは、バリデーションの結果が正常でないことを確認しています。その後、各項目のエラー数をチェックしています。

次に、メールアドレスでない場合のテスト。

describe 'エラーチェック' do
  context '何も入れない場合' do
    subject { User.new }
    it 'エラーになること' do
      # 略
    end
  end
  context 'メールアドレスでない場合' do
    subject { FactoryGirl.build(:user, email: 'invalid_email') }
    it 'エラーになること' do
      expect(subject).not_to be_valid
      expect(subject).to have(1).errors_on(:email)
    end
  end
  context 'パスワードと確認用パスワードが一致しない場合' do
    it 'エラーになること'
  end
  context 'メールアドレスが重複する場合' do
    it 'エラーになること'
  end
end

FactoryGirl.buildで、2つめの引数にハッシュを渡しています。これは、FactoryGirlで作られるオブジェクトの項目を一部上書きすることができます。メールアドレスに適当な文字列を渡すようにしています。そして、正常でないことを確認して、emailがエラーを1つ持っていることを確認します。

次に、パスワードが一致しないとエラーになるテスト。

describe 'エラーチェック' do
  context '何も入れない場合' do
    subject { User.new }
    it 'エラーになること' do
      # 略
    end
  end
  context 'メールアドレスでない場合' do
    subject { FactoryGirl.build(:user, email: 'invalid_email') }
    it 'エラーになること' do
      # 略
    end
  end
  context 'パスワードと確認用パスワードが一致しない場合' do
    subject { FactoryGirl.build(:user,
                                password: 'hogehoge',
                                password_confirmation: 'piyopiyo') }
    it 'エラーになること' do
      expect(subject).not_to be_valid
      expect(subject).to have(1).errors_on(:password)
    end
  end
  context 'メールアドレスが重複する場合' do
    it 'エラーになること'
  end
end

ほぼ、メールアドレスの時と同じようなテストです。コピペして該当箇所を書き換えるだけですが、ずいぶん安心感があります。ここで、FactoryGirlのモデル変更項目が2つになりました。2つ程度なら、まぁそんなに苦ではないですが、これからカラム数が増えて複雑化してくると大変です。そこでFactoryGirlで前に作った、パスワードが違うモデルを使ってみましょう。

context 'パスワードと確認用パスワードが一致しない場合' do
  subject { FactoryGirl.build(:user_invalid_password) }
  it 'エラーになること' do
    expect(subject).not_to be_valid
    expect(subject).to have(1).errors_on(:password)
  end
end

いろんな状態のモデルを作るのは、FactoryGirlに任せると楽ですが、さっきのメールアドレスのテストのように、1項目だけの変更だったりすると、逆に面倒だったりするので、そこは使い分けましょう。

最後にメールアドレスの重複のテストです。
肝は、beforeブロックで事前にデータをDBに登録しておくところです。そして、全く同じモデルオブジェクトをFactoryGirl.buildで作ります。そうすると、必然的にメールアドレスが同じモデルができます。これを検証にかけて、エラーになるかどうかを試します。

describe 'エラーチェック' do
  context '何も入れない場合' do
    subject { User.new }
    it 'エラーになること' do
      # 略
    end
  end
  context 'メールアドレスでない場合' do
    subject { FactoryGirl.build(:user, email: 'invalid_email') }
    it 'エラーになること' do
      # 略
    end
  end
  context 'パスワードと確認用パスワードが一致しない場合' do
    subject { FactoryGirl.build(:user_invalid_password) }
    it 'エラーになること' do
      # 略
    end
  end
  context 'メールアドレスが重複する場合' do
    before do
      FactoryGirl.create(:user)
    end
    subject { FactoryGirl.build(:user) }
    it 'エラーになること' do
      expect(subject).not_to be_valid
      expect(subject).to have(1).errors_on(:email)
    end
  end
end

できました。シンプルなデータのマスターとかだと、これくらいのテストで済みます。

新しい制約ができたら?

たとえば、パスワードはこのままだと空文字じゃなければ1文字でもよくなる。せめて8文字以上にしたい。半角英数字と記号のみにしたい。という要望が出たとしたら、それもまたとりあえずテストに落とし込みます。

describe 'エラーチェック' do
  # 略
  context 'パスワードと確認用パスワードが一致しない場合' do
    subject { FactoryGirl.build(:user_invalid_password) }
    it 'エラーになること' do
      # 略
    end
  end
  context 'パスワードが短すぎる場合' do
    it 'エラーになること'
  end
  context 'パスワードに全角が含まれる場合' do
    it 'エラーになること'
  end
  # 略
end

これでもいいですが、パスワードが、パスワードがってうるさいですね。まとめましょう。

describe 'エラーチェック' do
  # 略
  describe 'パスワードが' do
    context '確認用パスワードと一致しない場合' do
      subject { FactoryGirl.build(:user_invalid_password) }
      it 'エラーになること' do
        # 略
      end
    end
    context '短すぎる場合' do
      it 'エラーになること'
    end
    context '全角が含まれる場合' do
      it 'エラーになること'
    end
  end
  # 略
end

こういうふうに、describeをネストすることで、テスト対象のスコープを狭くしていくことができます。テスト対象のスコープは常に意識しておきましょう。
他にも、似たような挙動を纏めるテストの書き方(shared_examples_for)や、遅延評価用変数の定義(let, let!)などありますが、それはおいおい覚えていってください。ググればでてくるでしょう。

正しいテストの書き方とは?

色々書いてきましたが、これが完全に正解かどうかは、わかりません。
私はテストしたいことがテストできていれば、書き方はどうでもいいと思います。上のような書き方は冗長じゃないか?と思う人もいるでしょう。自分で考えたり、チームで話し合って、わかりやすいものを採用すればそれでいいかなと思います。

長くなったので、インテグレーションテストなどについてはまた後で続きを書きます(違う記事にするかも)。


不格好経営を読み終えた。

正直私はDeNAとかGREEとかがあんまり好きではなかったので、この本も発売当初はなんとなくわざと遠ざけて読まなかった。
この前、GWのブックオフ20%オフセールでたまたま見つけたのだが、セールだしなーと、5冊買おうと思っていた最後の本として取っただけだった。しかし、内容はとっても面白かったので、経営者とかだけじゃなくてエンジニアの人が読んでも面白いと思う。

DeNAが生まれたのは、インターネットオークションサイトを立ち上げたいというところからだったようだ。マッキンゼーにいた南場さんがso-netに企画を提案していたら、あまりの熱意にso-netの人から「そんなに熱っぽく語るんなら自分でやったらどうか」と言われて、やることを決意したのがきっかけらしい。それがビッダーズだったようだ(ちなみに私はビッダーズ使ったことなかったですね…)。ビッダーズを立ち上げるにあたってもトラブルが続出して、そのあたりの話もとても面白かった。ビッダーズを作っている間にヤフオクができ、先を越されてしまい、2番になってしまう。赤字を流しながらも、オークションでの1番を目指すが、ヤフオクに勝てないことを悟ったら、今度は黒字化を目指すためにショッピングモールを充実させる。そっちは既に楽天が1番になっているが、とにかく黒字化を優先させた。

転機はモバイルにシフトしたときだった。モバオクで、モバイルでのオークションで1番を取った。そしてモバゲーの誕生。怪盗ロワイヤルはまだまだ記憶にあるゲームだ(※私はやったことないけど)。

自分の話になるが、よく考えたらモバゲーは登録はしていたが全然しなかった。GREEは友達付き合いもあってやってたけれど、無料の範囲だけでやってたし、mixiは招待制だった頃はメインで書き込んでいたし、mixiアプリが作れるようになった頃はアプリを作ろうとしてたので色々調べていたし、しょーもないmixiアプリを作ってみたりもしていた。DeNAとは本当に接点がなかった。モバゲーはCMがウザイと感じて嫌いになっていってた。

本の中では、事業の話と人材の話などが赤裸裸に書かれている。社風も伝わってくる。

フラットで、信頼して任せる文化。
責任が人を育てるのだろう。

事業の話だと、コンプガチャ規制の話や、出会い目的を撲滅するために取り組んだことなど、すごく書かれている。ニュースだけ見ると、そういうことへの改善の取り組みを真摯にしていたとしても、あまり取り上げられない。また事件が起きましたとかしか流れないので、そういうのも相まって私はモバゲーをどんどん嫌いになっていってた。例え中で使っている技術がすごかったとしても、情報弱者から搾取するのが仕事みたいなイメージで。
しかし実際には同じような事件が起きないような措置をして、出会い目的の人たちがどんどん編み出していく暗号を解読して対応していったりなど、被害件数は効果的に減っていっていたらしい。また、SNSでもあるので、十代の若者が交流する場所・文化を守りたくて、奔走していたらしい。ユーザーのほとんどが健全な使い方をしていても、母数が大きい分、被害件数は少なかったとしても結構あったということで、対応が無茶苦茶大変なのは容易に想像できる。

南場さんの旦那さんの病気のために、社長を退任しなければならなかった話や、そこの引き継ぎの話などもある。様々な苦難を乗り越えてきた話は、いいことしか書いてない成功本とは違い、熱を感じる。DeNAという会社はこんなに熱かったのかと、読みながら驚いた。そして任せられるから熱くなるシチュエーションが多いんだろうなと感じた。この本に出てくる人はスーパーマンばっかりか、という印象だが、組織が人を育てる成長スピードがずば抜けてるんだろう。。。

人材採用にはすごく力を入れているらしいし、欲しい人材は何年でも口説く。ある程度の情報がそろったら迅速に意思決定をする。

ぐっときたのは、『事業リーダーにとって、「正しい選択を選ぶ」ことは当然重要だが、それと同等以上に「選んだ選択を正しくする」ということが重要となる』という言葉だった。これは何事にも通じていると思うし、これを心がけているチームは強くなると思う。

終わりのほうで綴られているのは、日本から世界で成功しているIT企業がないので、それを目指すということだったが、それは応援したくなった。自分が持っていたDeNAの悪いイメージはだいぶ薄れた。まぁモバゲーはしないだろうけれど…。企業的にも挑戦的だけれど、エンジニアに挑戦させる文化を持っているのは、とても素晴らしく、とてもうらやましいことでもあった。そりゃ成功するわなー!と思わされた。もっとエンジニアというか、人に挑戦させる文化の会社が増えれば社会がよくなっていくのになーと思った。


オープンセミナー岡山2014に行ってきました。

オープンセミナー岡山に行ってきました。
今回は、テーマが『フロントエンドとサーバサイド』であったためか、
参加者が例年より多かったような気がします。

オープンセミナー2014@岡山のサイト

セッションの内容ですが、以下の順番でした。

1.パソコンは私たちのもう1つの目-見やすく、聞きやすいWebページづくりにぜひご協力を!
スピーカー:毛並 みけ
2.ソフトウェアエンジニアの目指す道〜フロントエンドとサーバーサイドを超えて
スピーカー:倉貫 義人
3.実践 大都会式 プロトタイピング&フロントエンド2014
スピーカー:前川 昌幸 & 久保木 博
4.オープンで柔軟なクラウドプラットフォーム ”Microsoft Azure“
スピーカー:井上 大輔
5.明日からできる、デザイン脳!
スピーカー:秋葉 ちひろ
6.ベンチャーのサービス開発におけるフロントエンドとサーバサイドの間
スピーカー:大場 光一郎

パソコンは私たちのもう1つの目-見やすく、聞きやすいWebページづくりにぜひご協力を!

毛並みけさんは視覚障害者で、普通にPCを使いこなしているように見えたので最初気付きませんでしたが、音声機能を使ってPCの操作をしていました。その立場で、どういうウェブサイトが視覚障害者とってイライラするか、優しいかを、音声ブラウザを使って紹介してくださいました。

悪い例のサイトは

  • 文章を画像で作ったものを貼付けただけのもの
  • 『こちら』にだけリンクを貼付けたもの(『詳細はこちら』にして!)
  • 本文にたどり着かないもの(メニュー、サイドバーの内容をずっと読まれてしまう)

などがありました。文章を画像で作っているパターンは論外ですが、『こちら』とメニューがだらだら長いパターンは結構やってしまっているかもしれません…。

逆に、いいサイトの場合は

  • サイトの一番最初に本文へのアンカーリンクがある
  • 目次が先頭にある

などでした。例として、岡山市のサイトを紹介されていましたが、一番上に本文へのリンクがあり、たしかにこれならすぐ本文にいけてユーザービリティが高いなと思わされました。

感想は、文章構造を意識してサイトは作っているものの、視覚障害者への配慮ができていただろうか?と考えるとまだまだできていなかったなと思わされました。html5勉強会などをしていても、「見え方としては別に変わらないからいいんじゃないの?」という意見とかもあったのですが、やっぱりマークアップの順番は大事であると思わされました。
それにしても、昨今のAJAXによるサイトとかの場合、どのようになるのかが気になります。turbolinkとかでページを切り替えた場合など、音声ブラウザだとどうなるんだろうか…。
「サイトは技術を見せる場所じゃなくて情報をわかりやすく見せる場所です」と言われていたのを、肝に命じようと思います。

ソフトウェアエンジニアの目指す道〜フロントエンドとサーバーサイドを超えて

倉貫さんは株式会社ソニックガーデンの社長で、近々、本が出版される模様です。

「納品」をなくせばうまくいく ソフトウェア業界の“常識”を変えるビジネスモデル

プログラマっていらなくなるの?ソフトウェアエンジニアってどういう職業なの?という内容でしたが、とても面白かったです。
昨今のソフトウェア開発では分業が進んでいったが、その結果どうなってしまったか。
いい?こととして、

  • コストの最適化
  • 人員の配分がしやすくなった
  • 品質が均質化した(安定とは違う)

悪いこととして、

  • 部分最適
  • モチベーションの低下
  • 成長意欲の奪取

などがあげられていました。

しかし、本来のソフトウェア開発に求められるものは、お客さんの問題を解決するところにあります。
ソフトウェア開発は製造業ではなく、問題解決の手段を提供するものであり、それは、

  • 再現性のないもの
  • ナレッジワーク
  • コンサルタントや医者のようなプロフェッショナルサービス

であると言われました。ここは、皆がうなずいてた部分だと思います。

「プロフェッショナルなサービスは、分業できるかといわれると、できない。研修医を100人集めても、難病を治せない。腕利きの名医が1人いなければならない。それはシステム開発も同じである。小規模な会社のほうが有利に働く」

たしかにその通りであり、おそらくプログラマはそれをわかっているんだけれど、一般的な認識だとそうでないと思うので、そこの認識を直していかなければならないと思いました。
あとの話はソニックガーデン ギルドの話(ソニックガーデン自身が大規模になるとサービスを提供できなくなるので、同じ志を持つ人・会社を増やしていくビジネスモデル)でした。

感想は、「人を増やしたらなんとかならんの?」みたいな認識の人はまだ大勢いるので、そこの認識を変えていく活動をしないといけないと特に感じました。よくあるソフトウェア業界をよくしていくには?的な本は、本来読んでもらいたい人が読まない、ということが多いと思います。倉貫さんの本がベストセラーになって、世間の認識が変わってきてくれたら嬉しいので、もう一度宣伝しときます。

もちろん、自分たちが変えていく、という志は持ち続けて、腕を磨き続けていきたいです。

実践 大都会式 プロトタイピング&フロントエンド2014

大都会勢の前川 昌幸さん & 久保木 博さんが、2人でプロトタイピングしてみた過程を話すという内容でした。
SNS経由でログインして、体重計測をするアプリを作ってみたということでしたが、

  1. ペーパープロトタイピングを行う
  2. 動きがないとわからないのでPOPというツールで動きを再現してみる
  3. 作り込んでいく

という感じだったかなと記憶してます(このときあんまりメモってなかった)。たしかSketch3も使ってました。
サーバサイドは前川さん、クライアントサイドは久保木さんが担当で、Node.jsを使い、デザインはbootstrapを使って、テンプレートエンジンにjade、JavaScript MVCにAngularJSを使ってました。このあたりで、クライアント側の技術としてsassの紹介や、AngularJSの話もありました。Tipsとして、

  • DNSの問い合わせの調整は大事
  • css spriteはどの程度までやるか(30kbまで)
  • DataUrlはどの程度までなら効果的か(大きくても数kbまで)
  • minifyは思ったほどは効果ないよ(やってやった感はある)
  • 自動化にはgruntやgulpを使ってた(慣れれば怖くない)

という話があり、前川さんが設定したgruntを久保木さんが使って便利だったそうです。

感想としては、やっぱりペーパープロトタイピングは大事だなという点。そして、アプリを作るには気持ちよい動作が大事になってくるので、その導線を確認するためにPOPを使って確認など、プロトタイピングをする手法も進化してるなぁと思いました。Sketch3はすごくよさそうだったので買いたいけれど結構な値段がしますね…。7,800円。gruntやgulpはやってみたい感じありますが、RubyistはMiddlemanがあるから、そこらへんも比較してみたいなと思いました。

オープンで柔軟なクラウドプラットフォーム ”Microsoft Azure“

Microsoftの井上さんの発表でした。Windows Azureの機能紹介でしたが、メチャクチャ多機能なので、ポイントを絞りながらの説明でした。
いきなり感想ですが、驚いたのがスピードです。スケールアップに2秒、スケールアウトとスケールアップに3秒など、まさに脅威のスピードでした。サポートしている言語環境も多く、まさに死角なし。よく比べられるAWSやGAEとの比較しての強みなどだと、IAASではなくPAASとしてスタートしているので、そちらのほうが強いということでした。ただ、IAASの機能もかなりパワーアップしているとのことでした。
Webサイトという機能の説明がありましたが、スケールアウトして4台のサーバがある状態でも、1つのソースコードを修正すれば、全てのサーバに反映されるなど、コード反映の手間もかからずにとても強力だと思いました。また、Visual Studio Onlineで、オンライン編集もできるというのがすごかったです。

明日からできる、デザイン脳!

一番聞きたいと思っていたセッションだったのが、秋葉ちひろさんのセッションでした。ちひろさんはシャープハッカソンでチームになったことがありますが、あれからどんどん実力と名声をあげていってすごいなーと…。セッションの内容もとても面白かったです。
このセッションを聞いたら即デザインができるわけではないよと断った上で、じゃあどういうことに気をつければいいか、どういうふうに言えばデザイナーに伝わるか、サンプルアプリを作るとしてデザインするとしたら、普段の心がけ、という感じでした。

サンプルアプリは歯科衛生士さんが、患者の予約を受ける用のアプリで、データは名前と生年月日と、以前にいつ来てどういう処置を受けたか、というものですが、ただ情報をそのまま出すのではく、見やすく加工したらどう変わるかや、見せるときの基本テクニックとかでした。余白をとること、情報のグルーピングをするなど。

感想ですが、ちょうど、ノンデザイナー・デザインブックを読み終わったばかりの自分としては、あー納得、ということが多かったです。ちょっとした心がけで使い勝手はすごく変わるというのは説得力ありました。特に、アプリだと、見た目だけではなく、『人が使う』ことを想定するデザインが大事で、本当に知りたい情報は何か、見やすいように加工するだけで全然変わるというのを目の当たりにして、まだまだ配慮が欠けてたなと実感しました。プログラマ脳というか、機械脳になっていると、機能にフォーカスしてしまい、使う人の目線を考えることができず、トイレの「流す」を「便器洗浄」にしてしまうという例の紹介がめっちゃ面白かったです。

ベンチャーのサービス開発におけるフロントエンドとサーバサイドの間

クラウドワークスのCTOの大場さんの発表でした。
クラウドワークスのサービスの紹介と、サービスが使いやすくなるための改善としてどういう取り組みをしているか、そしてバックエンドで使ってるツール・サービスの話など、実践的で面白い内容でした。

コミュニケーションはChatworkで行い、課題管理はRedMine、コード管理はGitHub、CIはSEMAPHORE、サーバはAWSを使っているようでした。SEMAPHOREは初めて聞きましたが、昨今はCIサービスの群雄割拠時代で、どれも結構いいらしいんですが、値段が安かったのと要件を満たしてたのでSEMAPHOREにしたそうです。CIはJenkinsを使うこともあったけれど、Jenkinsはサービスが成長してくるとテストが遅くなっていって、Jenkinsのお守りが大変になってくる。サービスの提供ペースは変えたくないのに、Jenkinsが足かせになってくるのは本末転倒だから、外部サービスにしたという話は、弊社のことも考えるとたしかになーと思いました。Jenkinsは、Jenkins職人がいないと辛いものがあるので(弊社はカズさんがいるからマジ助かってる)、人数が少ないところだと尚更後々辛くなりそうだなと思います。いや、Jenkinsは悪くないんだけれど。
そして、サービスの改善は、ユーザーの声を大事にするという点と、インターンの人たちの毒されていない目でサービスの悪いところを指摘してもらうというものでしたが、ここの指摘されている点が自分たちの作っているものにも当てはまりそうで、すごくためになりました。改善せな!と思わされました。そのときに大場さんが「機械脳になりすぎて便器洗浄したい系男子って感じ」と言って、大爆笑をさらっていました。さすがでした。

感想としては、日常の業務の中に改善するところを取り入れておくというのが非常に大事だなと思いました。日常に取り入れられていないと、後回しになって結局やらないというパターンになってしまい、どんどんユーザー体験が悪いものになっていってしまうんじゃないかなと思いました。改善は、あればいい、じゃなくて、やらなければならない。優先順位の高いものとして日常に取り入れるようにしていかないとダメだなと思ったので、チーム内でも話し合っていきたいと思いました。

この後は、岡山では恒例の座・スタジアムで懇親会となりましたが、人数めっちゃ多くて混雑してましたが、とても楽しかったです。LTも盛り上がっててよかったです。やっぱり人がLTしてるのを見ると、自分も何かネタ作ってやっておけばよかった…とか考えてしまうので、今度はなにかしらネタを作っておかなければ…。