関連記事
- 勉強会BDD<1>RSpecとCapybaraースタート
- 勉強会BDD<2>RSpecとCapybaraーモデル
- 勉強会BDD<3>RSpecとCapybaraーユーザー認証
- 勉強会BDD<4>RSpecとCapybaraーポイントシステム
この記事について
- この記事はoiaxというサイトから提供された記事を元に社内で勉強会を行う目的で作成した。
- 詳細な内容が知りたい場合はoiaxの記事を参考にしていただきたい。
- oiaxの記事と大きな違いは社内の開発ツールとしてvimを使っているので、vim環境ですぐスペックの確認ができるようにしている。
- そして、詳細説明は省略して実習モードで構成されている。
BDDで開発するためには
- ある機能のスペック(仕様)を先に洗い出す。
- そのスペックを元にテストコードを作成する。
- テストコードを書いたら、とりあえず実行して失敗になることを確認する。
- その後、機能を実装する。
- テストが成功するまで2~4を繰り返す。
モデルのテスト
Customerモデルの仕様
顧客モデルの仕様を先に洗い出す。
- 姓(family_name)、名(given_name)、姓フリガナ(family_name_kana)、名フリガナ(given_name_kana)が必須入力項目
- それぞれ40文字以内。
- 姓と名で許される文字の種類は、漢字、ひらがな、カタカナ。
- 姓フリガナと名フリガナはカタカナのみ。ただし、ひらがなでの入力も受け付けて、カタカナに自動交換する。
- いわゆる半角は全角カナに自動交換する。
Customerモデルの作成
railsのgeneratorを使って雛形を生成する。
# generate model
bin/rails g model customer
# db/migrate/..._create_customers.rb
class CreateCustomers < ActiveRecord::Migration
def change
create_table :customers do |t|
t.string :family_name, null: false
t.string :given_name, null: false
t.string :family_name_kana, null: false
t.string :given_name_kana, null: false
t.timestamps null: false
end
end
end
# migration
bin/rake db:migrate
bin/rake db:test:prepare
# spec/models/customer_spec.rb
require 'rails_helper'
RSpec.describe Customer, type: :model do
example 'family_nameは空であってはならない' do
customer = Customer.new(
family_name: '', given_name: '太郎',
family_name_kana: 'ヤマダ', given_name_kana: 'タロウ'
)
expect(customer).not_to be_valid
expect(customer.errors[:family_name]).to be_present
end
end
# ここまでしておいてテスト
# , + c
# app/models/customer.rb
class Customer < ActiveRecord::Base
validates :family_name, presence: true
end
# spec/models/customer_spec.rb
require 'rails_helper'
RSpec.describe Customer, type: :model do
%w{family_name given_name family_name_kana given_name_kana}.each do |column_name|
example "#{column_name}は空であってはならない" do
customer = Customer.new(
family_name: '山田', given_name: '太郎',
family_name_kana: 'ヤマダ', given_name_kana: 'タロウ'
)
customer[column_name] = ''
expect(customer).not_to be_valid
expect(customer.errors[column_name]).to be_present
end
end
end
# app/models/customer.rb
class Customer < ActiveRecord::Base
validates :family_name, presence: true
validates :given_name, presence: true
validates :family_name_kana, presence: true
validates :given_name_kana, presence: true
end
# もう一度テスト
# , + c
letメソッドとFactory Girl
妥当なオブジェクトのテストを追加する。
# spec/models/customer_spec.rb
require 'rails_helper'
RSpec.describe Customer, type: :model do
example '妥当なオブジェクト' do
customer = Customer.new(
family_name: '山田', given_name: '太郎',
family_name_kana: 'ヤマダ', given_name_kana: 'タロウ'
)
expect(customer).to be_valid
end
%w{family_name given_name family_name_kana given_name_kana}.each do |column_name|
example "#{column_name}は空であってはならない" do
customer = Customer.new(
family_name: '山田', given_name: '太郎',
family_name_kana: 'ヤマダ', given_name_kana: 'タロウ'
)
customer[column_name] = ''
expect(customer).not_to be_valid
expect(customer.errors[column_name]).to be_present
end
end
end
let methodを利用して重複を解消する。
- letはメモかされたヘルパーメソッドを定義するメソッド。
- letで定義されたメソッドは各exampleごとに読み出される。(各exampleごとにインスタンスを作り直す。)
# spec/models/customer_spec.rb
require 'rails_helper'
RSpec.describe Customer, type: :model do
let(:customer) do
Customer.new(
family_name: '山田', given_name: '太郎',
family_name_kana: 'ヤマダ', given_name_kana: 'タロウ'
)
end
example '妥当なオブジェクト' do
expect(customer).to be_valid
end
%w{family_name given_name family_name_kana given_name_kana}.each do |column_name|
example "#{column_name}は空であってはならない" do
customer[column_name] = ''
expect(customer).not_to be_valid
expect(customer.errors[column_name]).to be_present
end
example "#{column_name}は40文字以内" do
customer[column_name] = 'ア' * 41
expect(customer).not_to be_valid
expect(customer.errors[column_name]).to be_present
end
end
end
# app/models/customer.rb
class Customer < ActiveRecord::Base
validates :family_name, presence: true, length: {maximum: 40}
validates :given_name, presence: true, length: {maximum: 40}
validates :family_name_kana, presence: true, length: {maximum: 40}
validates :given_name_kana, presence: true, length: {maximum: 40}
end
Factory Girlを利用する。
- テストをするたびにCustomer.newやCustomer.createに属性と値のハッシュを与えて、妥当なCustomerオブジェクトを作るは冗長。
- これを解消するためにファクトリーを定義して使う。
# spec/factories/customers.rb
FactoryGirl.define do
factory :customer do
family_name '山田'
given_name '太郎'
family_name_kana 'ヤマダ'
given_name_kana 'タロウ'
end
end
# spec/models/customer.rb
require 'rails_helper'
RSpec.describe Customer, type: :model do
let(:customer) { FactoryGirl.build(:customer) }
example '妥当なオブジェクト' do
expect(customer).to be_valid
end
%w{family_name given_name family_name_kana given_name_kana}.each do |column_name|
example "#{column_name}は空であってはならない" do
customer[column_name] = ''
expect(customer).not_to be_valid
expect(customer.errors[column_name]).to be_present
end
example "#{column_name}は40文字以内" do
customer[column_name] = 'ア' * 41
expect(customer).not_to be_valid
expect(customer.errors[column_name]).to be_1. present
end
end
end
モデルテストの演習
文字の種類を制限する。
# spec/models/customer_spec.rb
require 'rails_helper'
RSpec.describe Customer, type: :model do
let(:customer) { FactoryGirl.build(:customer) }
example '妥当なオブジェクト' do
expect(customer).to be_valid
end
%w{family_name given_name family_name_kana given_name_kana}.each do |column_name|
example "#{column_name}は空であってはならない" do
customer[column_name] = ''
expect(customer).not_to be_valid
expect(customer.errors[column_name]).to be_present
end
example "#{column_name}は40文字以内" do
customer[column_name] = 'ア' * 41
expect(customer).not_to be_valid
expect(customer.errors[column_name]).to be_present
end
end
%w{family_name given_name}.each do |column_name|
example "#{column_name}は漢字、ひらがな、カタカナを含んでもよい" do
customer[column_name] = '亜あア'
expect(customer).to be_valid
end
example "#{column_name}は漢字、ひらがな、カタカナ以外の文字を含まない" do
['A', '1', '@'].each do |value|
customer[column_name] = value
expect(customer).not_to be_valid
expect(customer.errors[column_name]).to be_present
end
end
end
end
# app/models/customer.rb
class Customer < ActiveRecord::Base
validates :family_name, presence: true, length: {maximum: 40},
format: {with: /\A[\p{Han}\p{Hiragana}\p{Katakana}]+\z/, allow_blank: true}
validates :given_name, presence: true, length: {maximum: 40},
format: {with: /\A[\p{Han}\p{Hiragana}\p{Katakana}]+\z/, allow_blank: true}
validates :family_name_kana, presence: true, length: {maximum: 40}
validates :given_name_kana, presence: true, length: {maximum: 40}
end
正規表現マッチ
\A: 文字列の先頭
\z: 文字列の末尾
\p{Han}: 任意の漢字
\p{Hiragana}: 任意のひらがな
\p{Katakana}: 任意のカタカナ
eqマッチャ
- 未実装の仕様
- 性と名で許される文字の種類は、漢字、ひらがな、カタカナ。
- 姓フリガナ名フリガナはカタカナのみ。ただし、ひらがなでの入力も受け付けて、カタカナに自動交換する。
- いわゆる半角カナは全角カナに自動交換する。
- テストケースを追加
# spec/models/customer_spec.rb
require 'rails_helper'
RSpec.describe Customer, type: :model do
let(:customer) { FactoryGirl.build(:customer) }
example '妥当なオブジェクト' do
expect(customer).to be_valid
end
%w{family_name given_name family_name_kana given_name_kana}.each do |column_name|
example "#{column_name}は空であってはならない" do
customer[column_name] = ''
expect(customer).not_to be_valid
expect(customer.errors[column_name]).to be_present
end
example "#{column_name}は40文字以内" do
customer[column_name] = 'ア' * 41
expect(customer).not_to be_valid
expect(customer.errors[column_name]).to be_present
end
end
%w{family_name given_name}.each do |column_name|
example "#{column_name}は漢字、ひらがな、カタカナを含んでもよい" do
customer[column_name] = '亜あア'
expect(customer).to be_valid
end
example "#{column_name}は漢字、ひらがな、カタカナ以外の文字を含まない" do
['A', '1', '@'].each do |value|
customer[column_name] = value
expect(customer).not_to be_valid
expect(customer[column_name]).to be_present
end
end
end
%w(family_name_kana given_name_kana).each do |column_name|
example "#{column_name}に含まれるひらがなはカタカナに交換して受け入れる" do
customer[column_name] = 'あいう'
expect(customer).to be_valid
expect(customer[column_name]).to eq('アイウ')
end
end
end
# app/models/customer.rb
require 'nkf'
class Customer < ActiveRecord::Base
validates :family_name, presence: true, length: {maximum: 40},
format: {with: /\A[\p{Han}\p{Hiragana}\p{Katakana}]+\z/, allow_blank: true}
validates :given_name, presence: true, length: {maximum: 40},
format: {with: /\A[\p{Han}\p{Hiragana}\p{Katakana}]+\z/, allow_blank: true}
validates :family_name_kana, presence: true, length: {maximum: 40}
validates :given_name_kana, presence: true, length: {maximum: 40}
before_validation do
self.family_name_kana = NKF.nkf('-wh2', family_name_kana) if family_name_kana
self.given_name_kana = NKF.nkf('-wh2', given_name_kana) if given_name_kana
end
end
補遺
- \p{Hiragana}及び\p{Katakana}には長音符が含まれていない。
- テストケースに長音符を入れるとエラーになるので、修正する。
diff --git a/spec/models/customer_spec.rb b/spec/models/customer_spec.rb
index 0d24227..9c35634 100644
--- a/spec/models/customer_spec.rb
+++ b/spec/models/customer_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe Customer, type: :model do
%w{family_name given_name}.each do |column_name|
example "#{column_name}は漢字、ひらがな、カタカナを含んでもよい" do
- customer[column_name] = '亜あア'
+ customer[column_name] = '亜あアーン'
expect(customer).to be_valid
end
diff --git a/app/models/customer.rb b/app/models/customer.rb
index ee92520..f83df81 100644
--- a/app/models/customer.rb
+++ b/app/models/customer.rb
@@ -2,9 +2,9 @@ require 'nkf'
class Customer < ActiveRecord::Base
validates :family_name, presence: true, length: {maximum: 40},
- format: {with: /\A[\p{Han}\p{Hiragana}\p{Katakana}]+\z/, allow_blank: true}
+ format: {with: /\A[\p{Han}\p{Hiragana}\p{Katakana}\u30fc]+\z/, allow_blank: true}
validates :given_name, presence: true, length: {maximum: 40},
- format: {with: /\A[\p{Han}\p{Hiragana}\p{Katakana}]+\z/, allow_blank: true}
+ format: {with: /\A[\p{Han}\p{Hiragana}\p{Katakana}\u30fc]+\z/, allow_blank: true}
validates :family_name_kana, presence: true, length: {maximum: 40}
validates :given_name_kana, presence: true, length: {maximum: 40}