【結論】
・プログラミングにおけるテストとは、
プログラミングが正常に動作するかを、
プログラミングで検証する手法
・人の手ではなく、プログラミングが検証する
メリットは下記の3つがあげられる
・検証する時間を短縮する
・仕様漏れを防ぐ
・リファクタリングや機能追加が容易になる
【目次】
- テストコードを書きました
- テストの目的
- RSpecとは
- テストの種類
- 0:モデルにバリデーションを設定する
- 1:gemの導入する
- 2:RSpecの設定をする
- 3:RSpecが正常に動作するか確認する
- 4:ダミーのインスタンスを作成する
- 5:テストコードを記述する
- 6:テストを実行する
- 7:エラーを検証する
- 総括
- 《今日の学習進捗》
【本題】
テストコードを書きました
チーム開発において、ユーザーの新規登録機能を実装する際、
モデルに対してテストコードを記述する要件が設定されていた為、
今回はテストコードについてまとめることにしました。
テストの目的
そもそも、プログラミングにおけるテストとは、
プログラミングが正常に動作するかを、
プログラミングで検証する手法のことです。
それを実装する目的は、主に3点あります
・検証する時間を短縮する
ユーザーの新規登録機能など、人の手で検証し出すと、
一回だけなら大きな負担にならないですが、
複数回に亘って検証を行うとなると、かなり手間が掛かります。
テストコードを書けば、この作業を省くことが出来ます。
・仕様漏れを防ぐ
テストコードを書く場合、どういった動作が正常なのかを
一つづつ洗い出す必要がありますが、その過程で仕様についての
理解が深まり、結果仕様漏れが発生することを防げます。
・リファクタリングや機能追加が容易になる
テストが一度通れば、その結果を維持さえすれば、
正常に動作するので、リファクタリングや機能追加に注力出来ます。
テストの種類
テストには、下記の二種類があります。
・単体テスト・・・一つのプログラムが単体で正常に動作するか検証する
・統合テスト・・・複数のプログラムが連動して行われる処理が正常に行われるか検証する
今回は、ユーザーを新規登録する際の
モデルの単体テストの記述方法を紹介します。
0:モデルにバリデーションを設定する
今回はモデルの単体テストなので、
まずモデルのバリデーションを記述します。
reg_mail_address = /\A[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*\z/ reg_alphanumeric_6characters = /\A[a-zA-Z0-9]{6,}+\z/ reg_prefecture_choce = /\A(?!.*--未選択--).*\z/ reg_only_kana = /\A[ァ-ヴ]+\z/ reg_zip_code = /\A[0-9]{3}\-[0-9]{4}+\z/ reg_intger_10or11_characters = /\A[0-9]{10,11}+\z/ reg_intger_14or16_characters = /\A[0-9]{14,16}+\z/ reg_intger_3or4_characters = /\A[0-9]{3,4}+\z/ validates :nickname, presence: true, uniqueness: true validates :email, presence: true, uniqueness: true, format: { with: reg_mail_address } validates :password, presence: true, confirmation: true, format: { with: reg_alphanumeric_6characters } validates :tel_confirmation, presence: true, uniqueness: true, format: { with: reg_intger_10or11_characters } validates :first_name, presence: true validates :last_name, presence: true validates :first_name_kana, presence: true, format: { with: reg_only_kana } validates :last_name_kana, presence: true, format: { with: reg_only_kana } validates :zip, presence: true, format: { with: reg_zip_code } validates :prefecture, presence: true, format: { with: reg_prefecture_choce } validates :city, presence: true validates :block, presence: true validates :card_number, presence: true, uniqueness: true, format: { with: reg_intger_14or16_characters } validates :expiration_month, presence: true validates :expiration_year, presence: true validates :security_code, presence: true, format: { with: reg_intger_3or4_characters }
1:gemの導入する
gemの「rspec-rails」「factory_girl」を追加します。
※rspec-rails・・・RSpecを利用する為のGem
※factory_girl・・・簡単にダミーのインスタンスを作成することができるGem
また web_console というgemはtest環境で動かすと
不具合が起きる可能性があるgemなのでdevelopment環境でのみ動くようにします。
新しく group :development do ~ end というブロックを作成し、その間に移動させましょう。
group :development, :test do gem 'rspec-rails' gem 'factory_girl_rails', "~> 4.4.1" end group :development do gem 'web-console', '~> 2.0' end
Gemfileを編集したら、忘れずにbundle installをしましょう。
2:RSpecの設定をする
下記でRSpecに必要な設定ファイルを作成します。
rails g rspec:install
下記のファイルが生成されます
create .rspec # create spec # create spec/spec_helper.rb #RSpecをRails無しで利用する際の共通設定を書くファイル create spec/rails_helper.rb #RailsにおいてRSpecを利用する際の共通設定を書くファイル
続いて、「.rspec」に下記の追記します。
--format documentation
これは、テストの結果をどの様に出力するか指定するコードです
下記の種類があります。
progress (もしくはp)
documentation (もしくはd)
html (もしくはh)
json (もしくはj)
3:RSpecが正常に動作するか確認する
まず、RSpecが正常に動作するか確認する為、
ターミナルで、下記を実行します。
bundle exec rspec
何もテストコードを記述していないので、
下記の様な表示が出ます。
No examples found. Finished in 0.00031 seconds (files took 0.19956 seconds to load) 0 examples, 0 failures
4:ダミーのインスタンスを作成する
specディレクトリ直下に「factories」というディレクトリを追加し、
その中に「users.rb」という名前でファイルを作成します。
FactoryGirl.define do factory :user do nickname "山田SAMURAI" email "yamada@gmail.com" password "00000000" password_confirmation "00000000" tel_confirmation "08011223344" first_name "山田" last_name "太郎" first_name_kana "ヤマダ" last_name_kana "タロウ" zip "123-4567" prefecture "三重県" city "山々市" block "大奥町1-2" building "コペンハーゲンハイツ102" phone_number "0595332211" card_number "4343565688779988" expiration_month "1900-04-01" expiration_year "2020-01-01" security_code "123" end end
ここでの記述内容を基に、
テストを行う為のダミーデータを生成出来ます。
5:テストコードを記述する
次にテストコードを記述します。
require 'rails_helper' describe User do describe '#create' do it "is valid with factory girl data" do user = build(:user) expect(user).to be_valid end it "is invalid without a nickname" do user = build(:user, nickname: nil) user.valid? expect(user.errors[:nickname]).to include("can't be blank") end it "is invalid with a duplicate nickname" do create(:user) another_user = build(:user) another_user.valid? expect(another_user.errors[:nickname]).to include("has already been taken") end it "is invalid without a email" do user = build(:user, email: nil) user.valid? expect(user.errors[:email]).to include("can't be blank") end it "is invalid with a duplicate email address" do create(:user) another_user = build(:user) another_user.valid? expect(another_user.errors[:email]).to include("has already been taken") end it "is invalid with a email not includes @ " do user = build(:user, email: "aaaaa") user.valid? expect(user.errors[:email][0]).to include("is invalid") end it "is invalid with a email includes no character before @ " do user = build(:user, email: "@aaa") user.valid? expect(user.errors[:email][0]).to include("is invalid") end it "is invalid with a email includes no character after @ " do user = build(:user, email: "aaaa@") user.valid? expect(user.errors[:email][0]).to include("is invalid") end it "is invalid with a email includes non-alphanumeric characters " do user = build(:user, email: "aaあa@aaa") user.valid? expect(user.errors[:email][0]).to include("is invalid") end it "is invalid without a password" do user = build(:user, password: nil) user.valid? expect(user.errors[:password]).to include("can't be blank") end it "is invalid without a password_confirmation although with a password" do user = build(:user, password_confirmation: "") user.valid? expect(user.errors[:password_confirmation]).to include("doesn't match Password") end it "is invalid with a password that has less than 5 characters " do user = build(:user, password: "00000", password_confirmation: "00000") user.valid? expect(user.errors[:password][0]).to include("is too short") end it "is invalid without a tel_confirmation" do user = build(:user, tel_confirmation: nil) user.valid? expect(user.errors[:tel_confirmation]).to include("can't be blank") end it "is invalid without a tel_confirmation that has less than 9 characters " do user = build(:user, tel_confirmation: "123456789") user.valid? expect(user.errors[:tel_confirmation][0]).to include("is invalid") end it "is invalid without a tel_confirmation that has more than 12 characters " do user = build(:user, tel_confirmation: "123456789012") user.valid? expect(user.errors[:tel_confirmation][0]).to include("is invalid") end it "is invalid without a first_name" do user = build(:user, first_name: nil) user.valid? expect(user.errors[:first_name]).to include("can't be blank") end it "is invalid without a last_name" do user = build(:user, last_name: nil) user.valid? expect(user.errors[:last_name]).to include("can't be blank") end it "is invalid without a first_name_kana" do user = build(:user, first_name_kana: nil) user.valid? expect(user.errors[:first_name_kana]).to include("can't be blank") end it "is invalid without a first_name_kana includes non-KANA characters " do user = build(:user, first_name_kana: "カナa") user.valid? expect(user.errors[:first_name_kana][0]).to include("is invalid") end it "is invalid without a last_name_kana" do user = build(:user, last_name_kana: nil) user.valid? expect(user.errors[:last_name_kana]).to include("can't be blank") end it "is invalid without a last_name_kana includes non-KANA characters " do user = build(:user, last_name_kana: "カナa") user.valid? expect(user.errors[:last_name_kana][0]).to include("is invalid") end it "is invalid without a zip" do user = build(:user, zip: nil) user.valid? expect(user.errors[:zip]).to include("can't be blank") end it "is invalid with a zip includes other than integer or hyphen in the first 3 digits " do user = build(:user, zip: "12a-1234") user.valid? expect(user.errors[:zip][0]).to include("is invalid") end it "is invalid with a zip includes the first number is 4 digits or more " do user = build(:user, zip: "1234-1234") user.valid? expect(user.errors[:zip][0]).to include("is invalid") end it "is invalid with a zip includes the first number is 2 digits or less " do user = build(:user, zip: "12-1234") user.valid? expect(user.errors[:zip][0]).to include("is invalid") end it "is invalid with a zip includes other than integer or hyphen in the last 4 digits" do user = build(:user, zip: "123-123a") user.valid? expect(user.errors[:zip][0]).to include("is invalid") end it "is invalid with a zip includes the last number is 5 digits or more " do user = build(:user, zip: "123-12345") user.valid? expect(user.errors[:zip][0]).to include("is invalid") end it "is invalid with a zip includes the last number is 3 digits or less " do user = build(:user, zip: "123-123") user.valid? expect(user.errors[:zip][0]).to include("is invalid") end it "is invalid with a prefecture no chose " do user = build(:user, prefecture: "--未選択--") user.valid? expect(user.errors[:prefecture][0]).to include("is invalid") end it "is invalid without a city" do user = build(:user, city: nil) user.valid? expect(user.errors[:city]).to include("can't be blank") end it "is invalid without a block" do user = build(:user, block: nil) user.valid? expect(user.errors[:block]).to include("can't be blank") end it "is invalid without a card_number" do user = build(:user, card_number: nil) user.valid? expect(user.errors[:card_number]).to include("can't be blank") end it "is invalid with a card_number that has less than 13 characters " do user = build(:user, card_number: "1234567890123") user.valid? expect(user.errors[:card_number][0]).to include("is invalid") end it "is invalid with a card_number that has more than 17 characters " do user = build(:user, card_number: "12345678901234567") user.valid? expect(user.errors[:card_number][0]).to include("is invalid") end it "is invalid with a card_number includes non-integer characters" do user = build(:user, card_number: "12345678901234ab") user.valid? expect(user.errors[:card_number][0]).to include("is invalid") end it "is invalid with a duplicate card_number" do create(:user) another_user = build(:user) another_user.valid? expect(another_user.errors[:card_number]).to include("has already been taken") end it "is invalid without a expiration_month" do user = build(:user, expiration_month: nil) user.valid? expect(user.errors[:expiration_month]).to include("can't be blank") end it "is invalid without a expiration_year" do user = build(:user, expiration_year: nil) user.valid? expect(user.errors[:expiration_year]).to include("can't be blank") end it "is invalid without a security_code" do user = build(:user, security_code: nil) user.valid? expect(user.errors[:security_code]).to include("can't be blank") end it "is invalid with a security_code that has less than 2 characters " do user = build(:user, security_code: "12") user.valid? expect(user.errors[:security_code][0]).to include("is invalid") end it "is invalid with a security_code that has more than 5 characters " do user = build(:user, security_code: "12345") user.valid? expect(user.errors[:security_code][0]).to include("is invalid") end it "is invalid with a security_code includes non-integer characters" do user = build(:user, security_code: "12a") user.valid? expect(user.errors[:security_code][0]).to include("is invalid") end end end
6:テストを実行する
下記をターミナルで打ち込み、テストを実行します。
bundle exec rspec
7:エラーを検証する
エラーが発生した場合は、エラー文を確認し、
適宜「binding.pry」を噛ませて、
値が正常に取得できるか確認します。
総括
正直、テストコードは今まで全く分かりませんでしたが、
ここに来て理解が深まると共に、
テストの有用性を改めて知りました。
今後は、出来れば最初にテストを書ける様になりたいですね。
《今日の学習進捗》
チーム開発:13日目
ユーザー新規登録機能のバリデーションと
テストコードの記述が完了。
学習開始からの期間 :62日
今日までの合計時間:637h
今日までに到達すべき目標時間:566h
目標との解離:71h
「10,000時間」まで、
残り・・・「9363時間!」