6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2021年10月8日 JavaScript (JavaScript Primer) 変数と宣言

varの問題

同じ変数名で、再定義できてしまう点です。
letやconstでは、同じ名前の変数を再定義しようとすると、次のような構文エラー(SyntaxError)が発生します。

let word = "モスバーガー";
let word = "モスバーガー"; // SyntaxError: Identifier 'word' has already been declared

const food = "マック";
const food = "マック"; // SyntaxError: Identifier 'food' has already been declared

そのため、間違えて変数を二重に定義してしまうというミスを防ぐことができます。
またvarには変数の巻き上げと呼ばれる意図しない挙動があります。
後の章で詳しく学習するようなので、現時点では「letはvarを改善したバージョン」ということだけ覚えておくとよいと本書では言っています。
上記の理由から、varではなくlet constを使うべきです。
後方互換性を考慮し、varは使用可能になっています。

letとconstの違い

  • constは、再代入できない変数を宣言できます
  • letは、再代入ができる変数を宣言できます

constは再代入できない変数を定義するキーワードです。
再代入を禁止することで、ミスから発生するバグを減らすことが期待できます。
このため変数を宣言する場合には、まずconstで定義できないかを検討し、できない場合はletを使うことを推奨しています。

変数名の命名規則

変数を作成するときは、命名規則を守る必要があります。 JavaScript Primerに記載されている命名ルールを以下に引用します。

  1. 半角のアルファベット、_(アンダースコア)、$(ダラー)、数字を組み合わせた名前にする
  2. 変数名は数字から開始できない
  3. 予約語と被る名前は利用できない

変数名の最初の文字が数字でしたり、変数名が数字のみの場合は、変数宣言に失敗します。

const 2is = 0; // SyntaxError: Invalid or unexpected token

const 123 = "あいう" // SyntaxError: Unexpected number

また、予約語として定義されているキーワードは変数名として利用できません。予約語とは、あらかじめ使い方が決まっている単語です。

const for = 10; // SyntaxError: Unexpected token 'for'

constは厳密には定数ではない

本書の内容を噛み砕いて書いてみました。

1.定義したものが、可変な値(配列など) だった場合は、その中身となるものは書き換えが可能なので厳密には定数とは呼べないと言えます。 2.定義したものが、不変な値(文字列や数値) だった場合は、変更できないため定数と言えます。
ちなみに、同じ入力に対して常に同じ出力が保障されることを参照透過性といいます。

1の場合のコード例

const array = [];

array.push(5);

array.push(7);

array.push(7,8);

console.log(array);  

// [ 5, 7, 7, 8 ]←arrayは可変な値のため中身を書き換えられています。(定数とは言えない)

2の場合のコード例

const number = 10;  

// 10は数値であり、不変な値のため変更することができません。(定数と言えます)

まとめ

  • constは、再代入できない変数を宣言できる
  • letは、再代入ができる変数を宣言できる
  • varは、再代入ができる変数を宣言できるが、いくつかの問題が知られている
  • 変数の名前(識別子)には利用できる名前のルールがある
  • constは厳密に言うと定数ではない

参照

JavaScript Primer

2021年10月7日 JavaScript (JavaScript Primer) 基本文法

JavaScriptとは

JavaScriptはウェブブラウザ上で動くプログラミング言語です。

JavaScriptECMAScriptの仕様に従って動作しています。 ECMAScriptは毎年アップデートされます。2015年に大きくアップデート(ES2015)してJavaScriptは再度注目されるようになりました。

JavaScriptECMAScriptの関係とは?

JavaScriptという言語はECMAScriptという仕様によって動作が決められています。ECMAScriptという仕様では、どの実行環境でも共通な動作のみが定義されているため、基本的にどの実行環境でも同じ動作をします。

画像引用 つまり、ECMAScriptは、どの実行環境でも共通している部分の動作を担うもので、JavaScriptは、実行環境によって異なる部分のある機能も含んだものとなっています。JavaScriptECMAScriptを内包しているとイメージすると良いです。

基本的な文法

・ 大文字と小文字は区別する

// `name`という名前の変数を宣言
const name = "azu";
// `NAME`という名前の変数を宣言
const NAME = "azu";


・ 「文」はセミコロンで区切る

// 式や文の間にスペースがいくつあっても同じ意味となる
1 + 1;
1   +   1;

実行コンテキストとは

実行コンテキスト、スコープチェーン、JavaScript内部に詳しく記載されているで、引用します。

実行コンテキスト(EC) は、JavaScriptコードが実行される環境として定義されます。環境とは、JavaScriptコードが特定の時間にアクセスできるthis、変数、オブジェクト、および関数の値を意味します。

strict mode

use strictという文字列をファイルまたは関数の先頭に書くことで、そのスコープにあるコードはstrict modeで実行されます。 strict modeでは開発者が安全にコードを書けるようになっています。例えば、過去の技術や仕組みで構築されているシステムの機能や構文などを禁止したり、問題を含んだコードに対しては例外を投げることで間違いに気づきやすくしてくれる機能を提供しています。

"use strict";
// このコードはstrict modeで実行される

JavaScriptでのコメントの書き方

JavaScriptでもプログラムとしては評価されないが、ソースコードの説明などを付け足せるコメント機能があります。コメントには、1行コメント複数行コメントがあります。

// 一行コメント
// この部分はコードとして評価されない


/*
   複数行コメント
   囲まれている範囲がコードとして評価されない
 */


//HTML-likeコメント
<!-- この行はコメントと認識される
console.log("この行はJavaScriptのコードとして実行される");
-->  この行もコメントと認識される

上記の HTML-likeコメント とは、JavaScriptがサポートされていないブラウザでscriptタグを実行するためのコメントです。ES2015から追加されましたが、現在では全てのブラウザがJavaScriptに対応しているので、このコメント機能は不要です。

<script>
<!-- この行はコメントと認識される
console.log("この行はJavaScriptのコードとして実行される");
-->  この行もコメントと認識される
</script>

コメントに関するまとめ

  • // 以降から行末までが1行コメントです。
  • /**/で囲まれた範囲が複数行コメントです。
  • HTML-likeコメントは後方互換性のためだけに存在します。

参照

JavaScript Primer
JS-JavaScriptでコメントを記述する&コメントアウト活用法
実行コンテキスト、スコープチェーン、JavaScript内部

2021年10月7日 現場Rails Chapter5-16 Specが失敗したときの調査方法

Specが失敗した時の調査手順

Specが失敗した時に、以下の手順でエラーの原因を特定します。 手順3に関しては、モデルが正常に動作しているかを確認したい時に実行します。

  1. 失敗場所とエラーメッセージを確認する。

  2. 失敗場所とエラーメッセージを手がかりに原因を特定する。

  3. コンソールを使ってモデルの動きを確認する。

  4. スクリーンショットを活用する。

擬似的にSpecを失敗させる

今まで通っていたSpecが失敗するときは、プロダクトコードを変更したときが多いと考えられるので、擬似的に同様の状況を再現するためにapp/models/tasc.rbのバリデーションをコメントアウトします。

class Task < ApplicationRecord  
  # validates :name, presence: true, length: { maximum: 30 }

これにより、通っていたテストが通らなくなり、実際にコード変更を行なったときと同様のエラーメッセージを出せました。

コンソールのsandboxモードについて

コンソールのsandboxモードを使うと、コンソール内でデータベースの内容を変更しても最終的にロールバックして、起動前の状態に戻します。

sandboxの語源としては、砂場です。気軽に失敗のできる環境という意味合いです。

スクリーンショットを活用する

Specが失敗した際はスクリーンショット保存されるため確認することも出来ます。 ターミナル上で以下のようにエラー文に保存先が出力されます。

[Screenshot]: tmp/screenshots/ファイル名

スクリーンショットを見ることで得られる情報は多いため、原因調査の際にスクリーンショットを確認することは有効な手段ですので活用してみましょう。

参考

sandbox

2021年10月5日 現場Rails Chapter5-12 詳細表示機能のSpecを追加する

詳細表示機能Specを追加する

前回の一覧表示機能に続いて、詳細表示機能のテストコードを追記します。

describe 'タスク管理機能', type: :system do

  let!(:task_a) { FactoryBot.create(:task, name: '最初のタスク'), user: user_a)}

  ...省略
  
  describe '詳細表示機能' do
    context 'ユーザーAがログインしているとき' do
      let(:login_user) { user_a }
      
      before do
        visit task_path(task_a)
      end
      
      it 'ユーザーAが作成したタスクが表示される' do
        expect(page).to have_content '最初のタスク'
      end
    end
  end
end

let!を用いてtask_aを定義しておくと、タスク作成処理を共通化することが出来るため、一覧表示機能でタスクを作成する処理を記述する必要がなくなります。

shared_examplesを利用する

共通したitの内容をまとめる事ができる機能です。
今回の例の場合、詳細表示機能のテストと一覧表示機能のテストで itの内容が被ってしまっています。 その場合に、以下のようにshared_examplesを用います。

shared_examples_for 'ユーザーAが作成したタスクが表示される' do 
  it { expect(page).to have_content '最初のタスク' }  
end

このように共通化の記述をした上で、これまでitを書いていた場所には以下のように書きます。

it_behaves_like 'ユーザーAが作成したタスクが表示される'

使用上の注意としては、すぐに理解できる名前にし、可読性を損なわないようにする必要があります。

新規作成機能のSystem Spec

describe 'タスク管理機能', type: :system do
  describe '新規作成機能' do
    let(:login_user) { user_a }

    before do
      visit new_task_path
      fill_in 'Name', with: task_name
      click_button '登録する'
    end
    
    context '新規作成画面で名称を入力したとき' do
      let(:task_name) { '新規作成のテストを書く' }
      
      it '正常に登録される' do
        # .alert-successというCSSクラスを指定している
        # 特定のタグやCSS要素に特定の文字列が表示されていることを検証する
        expect(page).to have_selector '.alert-success', text: '新規作成のテストを書く'
      end
    end
    
    context '新規作成画面で名称を入力しなかったとき' do
      let(:task_name) { '' }
      
      it 'エラーとなる' do
        # 検証エラーを表示する領域内に、「名称を入力してください」というエラーメッセージが表示される。
        within '#error_explanation' do
          expect(page).to have_content '名称を入力してください'
        end
      end
    end
  end
end

withinメソッド

画面内の特定の範囲に絞って検証することが出来ます。 '名称を入力してください'というエラーメッセージが画面内に1つだけしか存在しない場合などを想定した上でerror_explanationというidの要素を指定して検証しています。

letの上書き

同じ名前のletを複数回定義すると、常に下の階層に定義したletが使われます。つまり、letは上書きができます。しかし、同じ名前のletを複数回定義すると、最終的にどのletが使われるのか分からなくなります。そのため、適切なバランスでletを使います。

describe 'タスク管理機能', type: :system do
  describe '新規作成機能' do  
    let(:login) { user_a }  
    let(:task_name) { '新規作成のテストを書く' } # デフォルトとして設定  

    before do  
      ...
    end

    context '新規作成画面で名称を入力したとき' do  
      it '正常に登録される' do  
        expect(page).to have_selector '.alert-success', text: '新規作成のテストを書く'
      end
    end  

    context '新規作成画面で名称を入力しなかったとき' do  
      let(:task_name) { '' } # 上書き  

      it 'エラーになる' do  
        within '#error_explanation' do  
          expect(page).to have_content '名称を入力してください'
        end
      end
    end
  end
end

参照

使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」

2021年10月4日 現場Rails Chapter5-9 他のユーザーが作成したタスクが表示されないことの確認

前回はユーザーAが作成したタスクがページに表示されていることを確認するテストを書きました。 今回はユーザーBがログインした際にユーザーAのタスクは表示されていないことを確認するテストを書いていきます。

require 'rails_helper'

describe 'タスク管理機能', type: :system do
  describe '一覧表示機能' do
    before do
    # user_aを名前とメールを指定して、新たに作成する
    user_a = FactoryBot.create(:user, name: 'ユーザーA', email: 'a@example.com')
    # user_aに紐づくタスクをタスク名を指定して作成する
    FactoryBot.create(:task, name: '最初のタスク', user: user_a)
    end
    
    context 'ユーザーAがログインしているとき' do
      before do
        # ログインページへアクセスする
        visit login_path
        # メールアドレスとパスワードをログインフォームに入力する
        fill_in 'メールアドレス', with: 'a@example.com'
        fill_in 'パスワード', with: 'password'
        # ログインボタンを押す
        click_button 'ログインする'
      end
      
      it 'ユーザーAが作成したタスクが表示される' do
        # pageに最初のタスクという内容があることを期待する。
        expect(page).to have_content '最初のタスク'
      end
   ーーーーーーーーーーーーーーーーーーーーーーー 
     # ここからが今回学んだ内容になります。
     context 'ユーザーBがログインしているとき' do
       before do
         Factorybot.create(:task, name: 'ユーザーB', email: 'a@example.com', user: user_a)
         visit login_path
         fill_in 'メールアドレス', with: login_user.email
         fill_in 'パスワード', with: login_user.password
         # ログインボタンを押す
         click_button 'ログイン'
       end
       
       context 'ユーザーAがログインしているとき' do
        let(:login_user){ user_a }
        
        it 'ユーザーAが作成したタスクが表示される'
          expect(page).to have_content '最初のタスク'
        end
        
      end
        
      # ユーザーBとしてログインしている為、ユーザーAが作成した最初のタスクが表示されないようにする
      it 'ユーザーAが作成したタスクが表示されない' do
       # '最初のタスク'が表示されない。
       expect(page).to have_no_content '最初のタスク'
      end
    end
  end
end

beforeを利用した共通化

コード自体の共通点が多い時、似たコードをまとめて、共通化することができます。今回は、beforeを使って共通化していきます。

describe 'タスク管理機能', type: :system do
   describe '一覧表示機能' do
     let(:user_a){ FactoryBot.create(:user, name: 'ユーザーA', email: 'a@example.com')}
     let(:user_b){ FactoryBot.create(:user, name: 'ユーザーB', email: 'b@example.com')}
     
     before do
       FactoryBot.create(:task, name: '最初のタスク', user: user_a)
       # ここでletのユーザーAが実際にデータベースに登録されます。
       visit login_path
       fill_in 'メールアドレス', with: login_user.email
       fill_in 'パスワード', with: login_user.password
       click_button 'ログインする'
     end
     
     context 'ユーザーAがログインしている時' do
       let(:login_user) { user_a }
       
       it 'ユーザーAが作成したタスクが表示される' do
         expect(page).to have_content '最初のタスク'
       end
     end
     
     context 'ユーザーBがログインしている時' do
       let(:login_user) { user_b }
       
       it 'ユーザーAが作成したタスクが表示されない' do
         expect(page).to have_no_content '最初のタスク'
       end
     end  
   end
end

否定系の書き方

以下の3種類がありますがどれを使用しても大丈夫です。

expect(page).to have_no_content '最初のタスク'
expect(page).not_to have_content '最初のタスク'
expect(page).to_not have_content '最初のタスク'

伊藤淳一さんはこちらのツイートで to_notが書きやすいとおっしゃっているので参考にしてみても良いかも知れません。


上記のコード(否定系の書き方の上)のように、同じ階層にあるすべてのcontext~end内で共通する処理は、一つ上の階層内で、beforeを定義して、共通処理を書くことが出来ます。
この場合は、context処理前にbeforeブロック内の処理が実行されます。ここで実行される処理は、パスワードやメールなどの値は実際に入っておらず、箱のような状態になっています。実際にcontext処理内のletで、before処理内のパスワードやメールの値が入ります。

letとlet!

現場Rails P.213~P.214に詳しく記載されてるので、以下に引用します。

letは呼び出されたタイミングで実行され、一度も呼び出されないときは実行されずじまいとなります。
そのため、例えばlogin_userというletを定義したとしても、以下の例のようにlogin_userを一度も呼び出さずにログインすると、ユーザーが作られることがありません。そのため、ログインが失敗してしまいます。

describe '一覧表示機能' do
  let(:login_user) { FactoryBot.create(:user, name: 'ユーザーA, email: 'a@exaple.com)}  
  
  before do  
    # let(:login_user)の定義を一度も呼び出さずにログインする
    visit login_path
    fill_in 'メールアドレス', with: 'a@example.com'
    fill_in 'パスワード', with: 'password'
    click_button 'ログインする' #=> ユーザーAが作られていないためにログイン失敗する
  end
  ...
end

このような場合は、letの代わりにlet!を利用すれば、beforeの前にユーザーが登録されるため、意図通りにログインできるようになります(参照しないのであればlet!にもせずにbeforeに記述しても良いという考え方もできますが、let!を使う方が読みやすくなる場合があります。また「呼び出されるケースと呼び出されないケースがあるが、データは常に作りたい」といった場合にも便利に利用できます)

参照

現場Rails(p.205~214)

RSpecで「~ではないこと」を検証するときは expect(x).to_not 、または expect(x).not_to のどちらを使うべきか?

使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」

RSpecのletを使うのはどんなときか?(翻訳)

【Rspec】ややこしいletの挙動についてまとめてみる。

2021年10月1日 現場Rails Chapter5-8 タスクの一覧表示機能のSystem Spec

タスクの一覧表示機能のSystem Spec

タスク一覧表示機能をテストする場合、以下のようなSystem Specを書きます。

ここではファクトリーに定義したテストユーザーをそのまま使わずに、"ユーザーA"を定義しています。今後、ユーザーBなど別のユーザーを扱う際にスムーズにするためです。

require 'rails_helper'

describe 'タスク管理機能', type: :system do
  describe '一覧表示機能' do
    before do
      # ユーザーAを作成
      # 作成者がユーザーAであるタスクを作成しておく
      user_a = FactoryBot.create(:user, name: 'ユーザーA', email: 'a@example.com')
      FactoryBot.create(:task, name: '最初のタスク', user: user_a)
    end
    context 'ユーザーAがログインしているとき' do
      before do
        # ユーザーAでログインする
      end
      
      it 'ユーザーAが作成したタスクが表示される' do
        # 作成済のタスクの名称が画面上に表示されていることを確認
      end
    end
  end
end

FactoryBotを用いてデータを作る

FactoryBotを用いてデータを作る場合、buildまたはcreateというメソッドを使います。

before do
  # テストユーザーを作成(今回の場合は、user_aを作成)
  user_a = FactoryBot.create(:user, name: 'ユーザーA', email: 'a@example.com')

  # 作成者がユーザーAであるタスクを作成しておく
  # userオプションを付けることで、taskファクトリーとuserの関連を指定している。=>user_aが作成したタスクになる
  FactoryBot.create(:task, :name: '最初のタスク', user: user_a)
end

今回のようにユーザーAという特定のユーザーを作りたいので、nameカラムやemailカラムを直接指定して変更しました。もしオプションを指定しない場合、FactoryBotの定義内で、設定された値でデータが作成されます。

# FactoryBotの定義
FactoryBot.define do
  factory :user do
    name { 'テストユーザ' }
    email { 'test1@example.com' }
    password { 'password' }
  end
end

また、タスクデータを作る際に、userオプションを指定しない場合は、新しいuserオブジェクトを合わせて作成します。つまり、毎回タスクオプジェクトを作る際に新たなuserオブジェクトも作成されます。

createとbuildの挙動の違い

createはオブジェクトを作成してデータベースに登録します。
buildはオブジェクトを作成するだけでデータベースに登録しません。
また、RSpecでのcreateはテスト終了時にDBが自動でロールバックされます。
これによりテストの度にFactoryBot.createを実行されても、また新たなデータとしてデータベースに登録できます。(ロールバックが行われないとDBエラーになります。)

createとbuildの使用用途の違い

DBの値を利用するテストが必要な場合にcreateを使います。
データの個数計算や一意のデータしか保存できないことなど、DBにアクセスして確認するテストにはcraeteを利用します。
DBにアクセスする必要がないテストの場合は、テストの実行速度を上げるためにbuildを利用します。

before(前提処理)にユーザーAとしてログインする処理を記載する

before do
  # ログインパスで、ログイン画面にアクセスする
  visit login_path
  # メールアドレスというラベル要素に入力値を入力する
  fill_in 'メールアドレス', with: 'a@example.com'
  # パスワードというラベル要素に入力値を入力する
  fill_in 'パスワード', with: 'password'
  # ログインボタンを押す
  click_button 'ログインする'
ene

visit

特定のURLでアクセスする操作です。visit[URL] で実現できます。今回のログイン画面へのアクセスではこのように書きます。

visit login_path  

fill_in

ラベルのついたテキストフィールドに値を入力する際に使用します。ラベルと入力値を指定して記述します。以下、メールアドレスでの例です。

fill_in 'メールアドレス', with: 'a@example.com'

click_button

ラベルのあるボタンを押します。以下、ログインボタンでの例です。

click_button 'ログイン'

it内の処理を記述する

it 'ユーザーAが作成したタスクが表示される' do
  # page(画面)に「最初のタスク」という内容があることを期待する
  # ここで、期待した処理がある時に、trueが返る
  expect(page).to have_content '最初のタスク'
end

今までで説明したコードを応用すると、タスク一覧表示機能のSystem Specは以下のようになります。

require 'rails_helper'

describe 'タスク管理機能', type: :system do
  describe '一覧表示機能' do
    before do
      user_a = FactoryBot.create(:user, name: 'ユーザーA', email: 'a@example.com')
      FactoryBot.create(:task, name: '最初のタスク', user: user_a)
    end
    
    context 'ユーザーAがログインしているとき' do
      before do
        visit login_path
        fill_in 'メールアドレス', with: 'a@example.com'
        fill_in 'パスワード', with: 'password'
        click_button 'ログインする'
      end
      
      it 'ユーザーAが作成したタスクが表示される' do
        expect(page).to have_content '最初のタスク'
      end
    end
  end
end

これでtasks_spec.rbのテストコードはひとまず完成しました。 以下のコマンドを実行することでテストが実行出来ます。

$ bundle exec rspec spec/system/tasks_spec.rb
$ bin/rspec 〜ようにbinでも実行可能です。

RSpecでの文法の捉え方

expect ~ to -


「~に-を期待する」 「I expect that ~.」を言い換えた「~に-」を期待するという場合に使います。

最後に...

今回のテストコードは書籍の序盤の内容に沿って書いたテストのため、まだまだ修正出来る点があるかと思います。 良ければ次回以降の記事も参考にしてください。

参照

本書のみ

2021年9月30日 現場Rails Chapter5 Rspecの基本形

Spec(テスト)の書き方  

Rspecでは、以下の基本フォーマットに従ってSpec(テスト)を書きます。

# テスト対象
describe '〜機能', type: :system do

  # 条件下で想定する動作 
  context '〇〇の場合' do 
  
    before do
      # テストコード実行前に実行する処理(前提条件)
    end
    
    # テストケース
    it '仕様の内容(期待の概要)' do
      # 期待通り、対象が動作するとSpec成功となり、想定通り動作しない場合、予期せぬ例外としてerrorが出ます。
      # また、例外ではなく想定と違う場合にFailureが結果として出ます。
    end
  end
end

Rspecの主要なメソッド  

主要メソッドの詳細を、以下にまとめます。

describe  

「何について仕様を記述しようとしているのか」を記述します。
System Specでは達成したい機能や処理の名前を記述するケースが多いです。

例)
掲示板検索機能、画像アップロード機能、etc

context

テストの内容を「状況・状態」のバリエーションごとに分類するために利用します。技術的にはdescribeとエイリアスとなっていて、テストケースを整理・分類するために用いられます。主に、describe内で使います。

例)
トップ画像を1枚選択してアップロード, トップ画像を2枚選択してアップロード

it  

itはテストケースを表します。itには "期待する動作を説明する文字列" と、"期待する動作" を書きます。

例)
トップ画像が登録されること
本文で絞り込み検索ができること (この場合はcontextは省略)
トップページに〇〇ボタンが表示されている (この場合contextは省略)

  it '〇〇する' do
    # 期待する動作
  end

before  

describeやcontext内にbeforeを記述すると、対応するdescribeやcontextの領域内のテストコードを実行する前に、beforeのブロック内に書かれたコードを実行してくれます。つまり、beforeはその領域全体の前提条件を実現するためのコードを記述する場所です。

本書ではまだ解説されていない内容にはなりますが、以下のように使われます。

  # ユーザーログインを行いユーザー詳細ページにアクセスしておきたいケース
  before do
    login(user)
    visit user_path
  end

同じ条件下で複数のテストケースを実行したい場合、beforeに書くことでDRYにできます。
beforeとitの関係については、現場Rails P.196の説明を引用します。

beforeの処理は、itが実行されるたびに新たに実行されます。次のitが実行されるまでにデータベースの状態は元に戻される為、あるテストケースのせいで別のテストケースが影響を受けるということは基本的には起きないようになっています。

FactoryBotとは?

Railsではテストで利用するデータベースとその他の目的で使うデータベースとを切り離して管理しています。そのため、テストで用いるテストデータを個別で作る必要があるのです。 FactoryBotはテストデータを簡単に作成するためのGemです。FactoryBotを使って、テストデータを作成する流れは以下のようになります。

  1. FactoryBotでデータを作成するためのテンプレートを用意します。
  2. System Specの適切なbefore(事前準備)などで、FactoryBotのテンプレートを使って、テスト用データベースにテストデータを投入します。
  3. 次のテストケースに進む前にデータ状態を戻す処理は、System Specが適切に処理を行ってくれます。

FactoryBotでテストデータを作成出来るように準備する

  • Userのファクトリを作成
# spec/factories/users.rb

FactoryBot.define do
  factory :user do
    name { 'テストユーザ' }
    email { 'test1@example.com' }
    password { 'password' }
  end
end

上記では、factoryというメソッドを利用して、Userクラスのファクトリ(テストデータ)を定義しています。

Userクラスでファクトリ名を異なる名前にする場合はfactoryメソッドに対して:classオプションを指定します。

factory :admin_user, class: User do
 ...(省略)
  • Taskのファクトリを作成
# spec/factories/tasks.rb

FactoryBot.define do
  factory :task do
    name { 'テストを書く' }
    description { 'RSpec & Capybara & FactoryBotを準備する' }
    # userは assosiation :userの省略形です。
    user
<200b>  end
end

先程の例でUserクラス側でファクトリ名を異なる名前にした場合は、下記のように記述します。

assosiasion :user, factory: :admin_user
# Userクラスのadmin_userというファクトリを紐付けます。
# ここでもassosiationを省略してuser factory: :admin_user とも書けます。

traitとは?

traitとは、FactoryBotで複数のサンプルデータを作成する際に使われるテクニックです。Factoryが複数ある場合、traitを使用することで記述の重複を減らすことができ、可読性を高めたコードにすることができます。

FactoryBot.define do
  factory :モデル名 do
    trait :上記モデルを継承したインスタンスの属性値 do
      #モデルの状態の違いによって、作成できるデータの場合分けができる
    end
    trait :上記モデルを継承したインスタンスの属性値 do
  
    end
    
  end
end

以下の例は、userモデルが一般の場合(traitを付けない時)と、:editorの場合と、:writerの場合によって作成されるデータを分けています。 実際に、テストデータを作成する時に、:editorや:writerなどの属性値を渡すことで、traitで設定した適切なデータを作成することが出来ます。

FactoryBot.define do
  factory :user do
    id { '1' }
    name { 'admin' }
    crypted_password { User.encrypt('password') }
    role { :admin }

    trait :editor do
      id { '2' }
      name { :editor }
      crypted_password { User.encrypt('password') }
      role { :editor }
    end

    trait :writer do
      id { '3' }
      name { :writer }
      crypted_password { User.encrypt('password') }
      role { :writer }
    end
  end
end


# writer属性を設定したtraitのテストデータを作成
$ user1 = factory(:user, :writer)`

読み進めていく中でGemfileでgemのバージョンを指定する記号の意味について疑問に思ったので調べました  

バージョンを上げるには3つのバージョンを使って表記します。

バージョンは、x.y.zの形式で表すことが出来ます。

  • X: メジャーバージョン
    大規模な変更が起き、今までの過去のバージョンとの互換性が失われるときにメジャーバージョンの数字をあげます。

  • Y: マイナーバージョン
    過去のバージョンとの互換性は失われませんが、新規機能の開発や機能改善が行われたときにマイナーバージョンの数字をあげます。自分たちのプロジェクトの影響を最小限に抑えつつきちんとアップデートするのであればマイナーバージョンは上げましょう。

  • Z: パッチバージョン
    主にバグ修正のためのバージョン情報です。バグ修正が行われたときにパッチバージョンの数字をあげます。自分たちのプロジェクトで扱う際には必ずこのバージョンは上げたほうがいいです。

Xはメジャーバージョン
yはマイナーバージョン
zはパッチバージョン


# バージョンはBundlerにお任せ
gem 'faker'


# バージョンを1.7.2に固定
gem 'faker', '1.7.2'


# 1.7.2以上(上に制限はなし)
gem 'faker', '>= 1.7.2'


# 1.7.2かつ1.8未満
# バッチバージョンの変更はOK。ただし、マイナーバージョンとメジャーバージョンの変更は行わない
gem 'faker', '~> 1.7.2'


# 1.7以上かつ2.0未満
# バッチバージョンとマイナーバージョンは変更OK。たが、メジャーバージョンは変更は行わない。
gem 'faker', '~> 1.7' 


以上のように、バージョンを指定しないと、互換性が失われる場合があります。例えば、$ bundle updateを使う場合など。(メジャーバージョンが変わってしまうため) 互換性があるとは、古いバージョンから、新しいバージョンにアップデートしても大丈夫であるということです。マイナーバージョンを上げる時は互換性に影響がない場合が多いですが、メジャーバージョンを変えてしまうと互換性がなくなってしまうことが多いので、注意が必要です。

参照

「セマンティック バージョニング」を読んだのでバージョニングについてまとめた

RSpecの(describe/context/example/it)の使い分け

Gemfile の ~> という記号はどういう意味なのか

gem のversion指定 ~>>= の差

【RSpec初級編】Factoryの継承をスマートに記述できるtrait(トレイト)を使いこなそう!

FactoryBotでtraitを使おう

factory_bot

セマンティックバージョニングとは