0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

コーディング未経験のPO/PdMのためのRails on Dockerハンズオン vol.11 - Test coding -

Last updated at Posted at 2020-03-14

はじめに

第11回目ですね。
前回はテストを自動化するためにRSpecSeleniumCapybaraなどを導入しましたね。

今日は今まで作ってきたアプリケーションに対してテストコードをコーディングしていきます。
本当はアプリをコーディングする前にテストをコーディングしてRedのフェーズにするべきなのですが、
まぁ最初なのでご愛嬌ということでGreenの状態から始めましょう。

前回のソースコード

前回のソースコードはこちらに格納してます。今回のハンズオンからやりたい場合はこちらからダウンロードしてください。

どういうふうにテストコード書いてくの?

ここは人によってやりやすいようにでいいと思うのですが、このハンズオンでは基本的には作りたい機能(ユーザーストーリー)ごとにテストファイルを分けて記述していきます。
例えば、今までだと「サインアップ」とか「サインイン」とかそういうやつです。

例えば以下のようにテストシナリオを考えてみます。

1. ユーザーとして、ページにダイレクトアクセスしたい

  1. 未サインインのユーザーが、トップページにダイレクトアクセスしたとき、トップページが表示されること
  • 未サインインのユーザーが、サインアップページにダイレクトアクセスしたとき、サインアップページが表示されること
  • 未サインインのユーザーが、サインインページにダイレクトアクセスしたとき、サインインページが表示されること
  • 未サインインのユーザーが、ユーザー詳細ページにダイレクトアクセスしたとき、ユーザー詳細ページが表示されること
  • サインイン済のユーザーが、トップページにダイレクトアクセスしたとき、そのユーザーのユーザー詳細ページが表示されること
  • サインイン済のユーザーが、サインアップページにダイレクトアクセスしたとき、そのユーザーのユーザー詳細ページが表示されること
  • サインイン済のユーザーが、サインインページにダイレクトアクセスしたとき、そのユーザーのユーザー詳細ページが表示されること
  • サインイン済のユーザーが、ユーザー詳細ページにダイレクトアクセスしたとき、ユーザー詳細ページが表示されること

2. ユーザーとして、ヘッダーリンクからページ遷移できること

  1. 未サインインのユーザーが、トップページでヘッダーのロゴをクリックしたとき、トップページに遷移すること
  • 未サインインのユーザーが、トップページでヘッダーの「Home」リンクをクリックしたとき、トップページに遷移すること
  • 未サインインのユーザーが、トップページでヘッダーの「Sign in」リンクをクリックしたとき、サインインページに遷移すること
  • 未サインインのユーザーは、トップページでヘッダーに「Profile」リンクが存在しないこと
  • 未サインインのユーザーは、トップページでヘッダーに「Sign out」リンクが存在しないこと
  • 未サインインのユーザーが、サインアップページでヘッダーのロゴをクリックしたとき、トップページに遷移すること
  • 未サインインのユーザーが、サインアップページでヘッダーの「Home」リンクをクリックしたとき、トップページに遷移すること
  • 未サインインのユーザーが、サインアップページでヘッダーの「Sign in」リンクをクリックしたとき、サインインページに遷移すること
  • 未サインインのユーザーは、サインアップページでヘッダーに「Profile」リンクが存在しないこと
  • 未サインインのユーザーは、サインアップページでヘッダーに「Sign out」リンクが存在しないこと
  • 未サインインのユーザーが、サインインページでヘッダーのロゴをクリックしたとき、トップページに遷移すること
  • 未サインインのユーザーが、サインインページでヘッダーの「Home」リンクをクリックしたとき、トップページに遷移すること
  • 未サインインのユーザーが、サインインページでヘッダーの「Sign in」リンクをクリックしたとき、サインインページに遷移すること
  • 未サインインのユーザーは、サインインページでヘッダーに「Profile」リンクが存在しないこと
  • 未サインインのユーザーは、サインインページでヘッダーに「Sign out」リンクが存在しないこと
  • 未サインインのユーザーが、ユーザー詳細ページでヘッダーのロゴをクリックしたとき、トップページに遷移すること
  • 未サインインのユーザーが、ユーザー詳細ページでヘッダーの「Home」リンクをクリックしたとき、トップページに遷移すること
  • 未サインインのユーザーが、ユーザー詳細ページでヘッダーの「Sign in」リンクをクリックしたとき、サインインページに遷移すること
  • 未サインインのユーザーは、ユーザー詳細ページでヘッダーに「Profile」リンクが存在しないこと
  • 未サインインのユーザーは、ユーザー詳細ページでヘッダーに「Sign out」リンクが存在しないこと
  • サインイン済のユーザーが、ユーザー詳細ページでヘッダーのロゴをクリックしたとき、そのユーザーのユーザー詳細ページに遷移すること
  • サインイン済のユーザーは、ユーザー詳細ページでヘッダーに「Home」リンクが存在しないこと
  • サインイン済のユーザーは、ユーザー詳細ページでヘッダーに「Sign in」リンクが存在しないこと
  • サインイン済のユーザーが、ユーザー詳細ページでヘッダーの「Profile」リンクをクリックしたとき、そのユーザーのユーザー詳細ページに遷移すること
  • サインイン済のユーザーが、ユーザー詳細ページでヘッダーの「Sign out」リンクが存在すること

3. ユーザーとして、サインアップしたい

  1. 未サインインのユーザーが、トップページで「Sign up now!」ボタンを選択したとき、サインアップページに遷移すること
  • サインアップページで「お名前」を入力できること
  • サインアップページで「メールアドレス」を入力できること
  • サインアップページで「パスワード」を入力できること
  • サインアップページで「パスワード」はマスク化されること
  • サインアップページで「パスワード」を入力したユーザーが、「パスワードを表示する」チェックボックスにチェックをいれたとき、「パスワード」が表示されること
  • サインアップページで「パスワード」を入力したユーザーが、「パスワードを表示する」チェックボックスのチェックを外したとき、「パスワード」がマスク化されること
  • サインアップページで「お名前」を入力していないユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「お名前」未入力のエラーメッセージが表示されること
  • サインアップページで「お名前」を51文字以上入力したユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「お名前」文字数超過のエラーメッセージが表示されること
  • サインアップページで「メールアドレス」を入力していないユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「メールアドレス」未入力のエラーメッセージが表示されること
  • サインアップページで「メールアドレス」を256文字以上入力したユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「メールアドレス」文字数超過のエラーメッセージが表示されること
  • サインアップページで「メールアドレス」を誤ったフォーマットで入力したユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「メールアドレス」フォーマットチェックエラーのエラーメッセージが表示されること
  • サインアップページで「メールアドレス」がすでに登録済みのメールアドレスを入力したユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「メールアドレス」重複のエラーメッセージが表示されること
  • サインアップページで「パスワード」を入力していないユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「パスワード」文字数不足のエラーメッセージが表示されること
  • サインアップページで「パスワード」を5文字以下で入力したユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「パスワード」文字数不足のエラーメッセージが表示されること
  • サインアップページで「お名前」「メールアドレス」「パスワード」を正しく入力したユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは成功し、そのユーザーのユーザー詳細ページにサインイン済状態で遷移すること
  • サインアップに成功したユーザーは、遷移後のユーザー詳細ページで自分の入力した「お名前」を確認できること
  • サインアップに成功したユーザーは、遷移後のユーザー詳細ページで自分の入力した「メールアドレス」を確認できること
  • サインアップに成功したユーザーは、遷移後のユーザー詳細ページでウェルカムメッセージを確認できること
  • サインアップに成功したユーザーは、遷移後のユーザー詳細ページをリロードしたとき、ウェルカムメッセージを確認できなくなること
  • サインアップページで「登録済みの方はこちら」リンクを選択したとき、サインインページに遷移すること

4. ユーザーとして、サインインしたい

  1. サインインページで「メールアドレス」を入力できること
  • サインインページで「パスワード」を入力できること
  • サインインページで「パスワード」はマスク化されること
  • サインインページで「パスワード」を入力したユーザーが、「パスワードを表示する」チェックボックスにチェックをいれたとき、「パスワード」が表示されること
  • サインインページで「パスワード」を入力したユーザーが、「パスワードを表示する」チェックボックスのチェックを外したとき、「パスワード」がマスク化されること
  • サインインページで「メールアドレス」を入力していないユーザーが、「Sign in」ボタンをクリックしたとき、サインイン失敗のエラーメッセージが表示されること
  • サインインページで「メールアドレス」として登録されていないメールアドレスを入力したユーザーが、「Sign in」ボタンをクリックしたとき、サインイン失敗のエラーメッセージが表示されること
  • サインインページで「メールアドレス」は正しいが「パスワード」を入力していないユーザーが、「Sign in」ボタンをクリックしたとき、サインイン失敗のエラーメッセージが表示されること
  • サインインページで「メールアドレス」は正しいが「パスワード」が正しくないユーザーが、「Sign in」ボタンをクリックしたとき、サインイン失敗のエラーメッセージが表示されること
  • サインインページで「メールアドレス」「パスワード」に正しい値を入力したユーザーが、「Sign in」ボタンをクリックしたとき、サインイン済状態でそのユーザーのユーザー詳細ページに遷移すること
  • サインインに成功したユーザーは、遷移後のユーザー詳細ページでサインイン成功メッセージを確認できること
  • サインインに成功したユーザーは、遷移後のユーザー詳細ページをリロードしたとき、サインイン成功メッセージを確認できなくなること
  • サインインページで「登録がまだの方はこちら」リンクを選択したとき、サインアップページに遷移すること

5. ユーザーとして、サインアウトしたい

  1. サインイン済のユーザーが、ユーザー詳細ページでヘッダーの「Sign out」リンクをクリックしたとき、未サインイン状態になりトップページに遷移すること

6. ユーザーとして、他のユーザーの情報を閲覧したい

  1. ユーザーが、存在するユーザーのユーザー詳細ページにアクセスしようとしたとき、そのユーザーの「お名前」「メールアドレス」を確認できること
  2. ユーザーが、存在しないユーザーのユーザー詳細ページにアクセスしようとしたとき、エラーが発生すること

ざっとあげただけでもこれだけのテストシナリオがあります。
こんなにコード書かなきゃいけないのかよ!と思うかもしれませんが、コードを書かないと少しのリファクタリングの度にこれら全てのテストを手動で行わなければ安心してデプロイできないという修羅の道を選ぶことになります。
今日でテストコードへのハードルを爆下げして気軽にリファクタできるエンジニアをめざしましょう!

テストコードを書いていこう

ここから実際にテストコードを書いていきます。
上でナンバリングで章立てしてましたね。それごとにスペックファイルを作って管理します。

1. ユーザーとして、ページにダイレクトアクセスしたい

ファイル名は01_direct_access_spec.rbにしておきましょう。

$ mkdir spec/system/
$ touch spec/system/01_direct_access_spec.rb
01_direct_access_spec.rb
feature "ユーザーとして、ページにダイレクトアクセスしたい", type: :system do

  background do
    @user1 = User.create(name: "John Smith", email: "john@sample.com", password: "john1234")
    @user2 = User.create(name: "Taro Tanaka", email: "taro@sample.com", password: "taro1234")
  end
  
  scenario "未サインインのユーザーが、トップページにダイレクトアクセスしたとき、トップページが表示されること" do
    visit root_path
    expect(current_path).to eq root_path
  end

  scenario "未サインインのユーザーが、サインアップページにダイレクトアクセスしたとき、サインアップページが表示されること" do
    visit sign_up_path
    expect(current_path).to eq sign_up_path
  end

  scenario "未サインインのユーザーが、サインインページにダイレクトアクセスしたとき、サインインページが表示されること" do
    visit sign_in_path
    expect(current_path).to eq sign_in_path
  end

  scenario "未サインインのユーザーが、ユーザー詳細ページにダイレクトアクセスしたとき、ユーザー詳細ページが表示されること" do
    visit user_path(@user1)
    expect(current_path).to eq user_path(@user1)

    visit user_path(@user2)
    expect(current_path).to eq user_path(@user2)
  end

  feature nil, type: :system do

    background do
      visit sign_in_path
      fill_in :user_email, with: @user1.email
      fill_in :user_password, with: @user1.password
      click_on :sign_in_button      
    end

    scenario "サインイン済のユーザーが、トップページにダイレクトアクセスしたとき、そのユーザーのユーザー詳細ページが表示されること" do
      visit root_path
      expect(current_path).to eq user_path(@user1)
    end
  
    scenario "サインイン済のユーザーが、サインアップページにダイレクトアクセスしたとき、そのユーザーのユーザー詳細ページが表示されること" do
      visit sign_up_path
      expect(current_path).to eq user_path(@user1)
    end
  
    scenario "サインイン済のユーザーが、サインインページにダイレクトアクセスしたとき、そのユーザーのユーザー詳細ページが表示されること" do
      visit sign_in_path
      expect(current_path).to eq user_path(@user1)
    end
  
    scenario "サインイン済のユーザーが、ユーザー詳細ページにダイレクトアクセスしたとき、ユーザー詳細ページが表示されること" do
      visit user_path(@user1)
      expect(current_path).to eq user_path(@user1)
  
      visit user_path(@user2)
      expect(current_path).to eq user_path(@user2)
    end
      
  end

end

このテストをパスさせるために、サインインページに少し細工をします。

app/views/sessions/new.html.erb
- <%= form.submit "Sign in", class: "btn btn-primary form-control" %>
+ <%= form.submit "Sign in", class: "btn btn-primary form-control", id: :sign_in_button %>

id: :sign_in_buttonを追記しました。これでid属性を追加できます。

まずはテストがパスするのを体感しましょうか!

$ docker-compose up -d
$ docker-compose exec web ash
# rspec spec/system/01_direct_access_spec.rb
Capybara starting Puma...
* Version 4.3.1 , codename: Mysterious Traveller
* Min threads: 0, max threads: 4
* Listening on tcp://127.0.0.1:45497
........

Finished in 11.85 seconds (files took 5.4 seconds to load)
8 examples, 0 failures

テストパスしましたね!
どんなテストが実行されたのか、ちょっと紹介させてください!

構文

前回も紹介しましたが、RSpecのシステムテストの構文は

feature "test name", type: :system do
  scenario "test scenario" do
    # テストコード
  end
end

です。scenarioは複数あります。

feature内の全てのscenarioに適用する初期条件を記述する場合はbackgroundを使います。

feature "test name", type: :system do
  background do
    # 前提条件
  end

  scenario "test scenario" do
    # テストコード
  end
end

また、featureを入れ子にすることも可能です。これによって特定のscenarioたちにだけ前提条件をつけることも可能です。

feature "test name", type: :system do
  scenario "test scenario" do
    # 前提条件が適用されない
  end

  feature "test detail name", type: :system do
    background do
      # 前提条件
    end

    scenario "test scenario" do
      # 前提条件が適用される    
    end
  end
end

まずはこの構文を身に付けましょう。

background

backgroundは前提条件を定義するためのブロックです。そのファイルのシナリオに共通して行われる処理をここで定義します。
よく使われる場面としては、今回のようにモデルを作っておく、とかですね。
モデルは今までのRubyコードと同じように、User.createUser.newが使えます。また、インスタンス変数にしないとscenario側では参照できないので注意してくださいね。

visit

visitは引数に名前付きルート(xxxx_path)やURLをとって、そこにアクセスします。

visit root_path

これでroot_path、つまり/にアクセスをしようとします。

expect().to

expect().to()内をto以降と検証します。
例えばexpect().to eq xxxxxのようにeqと組み合わせることで()内とxxxxxが等しいことを検証します。
この検証がfalseの場合はそのシナリオをFailureになります。

current_path

current_pathは現在表示されているパス(/とか/users/1とか)を取得します。

expect(current_path).to eq root_path

で、今表示されているページのパスがルートパス、つまりトップページであるかどうかを検証しているのです。

fill_in

fill_ininputに文字を入力する操作を実行します。

fill_in [id], with: [入力したい文字列]

で、ページからid属性が[id]input[入力したい文字列]を入力してくれます。

click_on

click_on<a>または<button>タグをクリックする操作を実行します。

click_on [id]

で、ページからid属性が[id]<a><button>タグをクリックしてくれます。

ここまでを理解すると

background do
  visit sign_in_path
  fill_in :user_email, with: @user1.email
  fill_in :user_password, with: @user1.password
  click_on :sign_in_button
end

が、サインインページにアクセスして@user1でサインインしようとしていることがわかりますね?

こんな感じで操作と検証を組み合わせてテストを自動化していきます!
ではどんどんテストコードを書いていきましょう!!

2. ユーザーとして、ヘッダーリンクからページ遷移できること

ファイル名は02_header_spec.rbでいきましょうか。

# touch spec/system/02_header_spec.rb

また少しidを仕込んでおきましょう。

app/views/layouts/application.html.erb
  <div class="container">
-   <%= link_to "sample app", root_path, class: "navbar-brand" %>
+   <%= link_to "sample app", root_path, class: "navbar-brand", id: :header_logo %>
    <ul class="navbar-nav">
      <% if signed_in? %>
        <%# サインイン済みの場合のリンク %>
-       <li class="nav-item"><%= link_to "Profile", current_user, class: "nav-link" %></li>
-       <li class="nav-item"><%= link_to "Sign out", sign_out_path, method: :delete, class: "nav-link" %></li>
+       <li class="nav-item"><%= link_to "Profile", current_user, class: "nav-link", id: :header_profile_link %></li>
+       <li class="nav-item"><%= link_to "Sign out", sign_out_path, method: :delete, class: "nav-link", id: :header_sign_out_link %></li>
      <% else %>
        <%# 未サインインの場合のリンク %>
-       <li class="nav-item"><%= link_to "Home", root_path, class: "nav-link" %></li>
-       <li class="nav-item"><%= link_to "Sign in", sign_in_path, class: "nav-link" %></li>
+       <li class="nav-item"><%= link_to "Home", root_path, class: "nav-link", id: :header_home_link %></li>
+       <li class="nav-item"><%= link_to "Sign in", sign_in_path, class: "nav-link", id: :header_sign_in_link %></li>
      <% end %>
    </ul>
  </div>

そしてテストシナリオを書きます。

spec/system/02_header_spec.rb
feature "ユーザーとして、ヘッダーリンクからページ遷移できること", type: :system do
  background do
    @user1 = User.create(name: "John Smith", email: "john@sample.com", password: "john1234")
    @user2 = User.create(name: "Taro Tanaka", email: "taro@sample.com", password: "taro1234")
  end

  scenario "未サインインのユーザーが、トップページでヘッダーのロゴをクリックしたとき、トップページに遷移すること" do
    visit root_path
    click_on :header_logo
    expect(current_path).to eq root_path
  end

  scenario "未サインインのユーザーが、トップページでヘッダーの「Home」リンクをクリックしたとき、トップページに遷移すること" do
    visit root_path
    click_on :header_home_link
    expect(current_path).to eq root_path
  end

  scenario "未サインインのユーザーが、トップページでヘッダーの「Sign in」リンクをクリックしたとき、サインインページに遷移すること" do
    visit root_path
    click_on :header_sign_in_link
    expect(current_path).to eq sign_in_path
  end

  scenario "未サインインのユーザーは、トップページでヘッダーに「Profile」リンクが存在しないこと" do
    visit root_path
    expect(page).not_to have_selector "#header_profile_link"
  end

  scenario "未サインインのユーザーは、トップページでヘッダーに「Sign out」リンクが存在しないこと" do
    visit root_path
    expect(page).not_to have_selector "#header_sign_out_link"
  end

  scenario "未サインインのユーザーが、サインアップページでヘッダーのロゴをクリックしたとき、トップページに遷移すること" do
    visit sign_up_path
    click_on :header_logo
    expect(current_path).to eq root_path
  end

  scenario "未サインインのユーザーが、サインアップページでヘッダーの「Home」リンクをクリックしたとき、トップページに遷移すること" do
    visit sign_up_path
    click_on :header_home_link
    expect(current_path).to eq root_path
  end

  scenario "未サインインのユーザーが、サインアップページでヘッダーの「Sign in」リンクをクリックしたとき、サインインページに遷移すること" do
    visit sign_up_path
    click_on :header_sign_in_link
    expect(current_path).to eq sign_in_path
  end

  scenario "未サインインのユーザーは、サインアップページでヘッダーに「Profile」リンクが存在しないこと" do
    visit sign_up_path
    expect(page).not_to have_selector "#header_profile_link"
  end

  scenario "未サインインのユーザーは、サインアップページでヘッダーに「Sign out」リンクが存在しないこと" do
    visit sign_up_path
    expect(page).not_to have_selector "#header_sign_out_path"
  end

  scenario "未サインインのユーザーが、サインインページでヘッダーのロゴをクリックしたとき、トップページに遷移すること" do
    visit sign_in_path
    click_on :header_logo
    expect(current_path).to eq root_path
  end

  scenario "未サインインのユーザーが、サインインページでヘッダーの「Home」リンクをクリックしたとき、トップページに遷移すること" do
    visit sign_in_path
    click_on :header_home_link
    expect(current_path).to eq root_path
  end

  scenario "未サインインのユーザーが、サインインページでヘッダーの「Sign in」リンクをクリックしたとき、サインインページに遷移すること" do
    visit sign_in_path
    click_on :header_sign_in_link
    expect(current_path).to eq sign_in_path
  end

  scenario "未サインインのユーザーは、サインインページでヘッダーに「Profile」リンクが存在しないこと" do
    visit sign_in_path
    expect(page).not_to have_selector "#header_profile_path"
  end

  scenario "未サインインのユーザーは、サインインページでヘッダーに「Sign out」リンクが存在しないこと" do
    visit sign_in_path
    expect(page).not_to have_selector "#header_sign_out_path"
  end

  scenario "未サインインのユーザーが、ユーザー詳細ページでヘッダーのロゴをクリックしたとき、トップページに遷移すること" do
    visit user_path(@user1)
    click_on :header_logo
    expect(current_path).to eq root_path
  end

  scenario "未サインインのユーザーが、ユーザー詳細ページでヘッダーの「Home」リンクをクリックしたとき、トップページに遷移すること" do
    visit user_path(@user1)
    click_on :header_home_link
    expect(current_path).to eq root_path
  end

  scenario "未サインインのユーザーが、ユーザー詳細ページでヘッダーの「Sign in」リンクをクリックしたとき、サインインページに遷移すること" do
    visit user_path(@user1)
    click_on :header_sign_in_link
    expect(current_path).to eq sign_in_path
  end

  scenario "未サインインのユーザーは、ユーザー詳細ページでヘッダーに「Profile」リンクが存在しないこと" do
    visit user_path(@user1)
    expect(page).not_to have_selector "#header_profile_link"
  end

  scenario "未サインインのユーザーは、ユーザー詳細ページでヘッダーに「Sign out」リンクが存在しないこと" do
    visit user_path(@user1)
    expect(page).not_to have_selector "#header_sign_out_link"
  end

  feature nil, type: :system do

    background do
      visit sign_in_path
      fill_in :user_email, with: @user1.email
      fill_in :user_password, with: @user1.password
      click_on :sign_in_button            
    end

    scenario "サインイン済のユーザーが、ユーザー詳細ページでヘッダーのロゴをクリックしたとき、そのユーザーのユーザー詳細ページに遷移すること" do
      visit user_path(@user2)
      click_on :header_logo
      sleep 1
      expect(current_path).to eq user_path(@user1)
    end

    scenario "サインイン済のユーザーは、ユーザー詳細ページでヘッダーに「Home」リンクが存在しないこと" do
      visit user_path(@user2)
      expect(page).not_to have_selector "#header_home_link"
    end

    scenario "サインイン済のユーザーは、ユーザー詳細ページでヘッダーに「Sign in」リンクが存在しないこと" do
      visit user_path(@user2)
      expect(page).not_to have_selector "#header_sign_in_link"
    end

    scenario "サインイン済のユーザーが、ユーザー詳細ページでヘッダーの「Profile」リンクをクリックしたとき、そのユーザーのユーザー詳細ページに遷移すること" do
      visit user_path(@user2)
      click_on :header_profile_link
      expect(current_path).to eq user_path(@user1)
    end

    scenario "サインイン済のユーザーが、ユーザー詳細ページでヘッダーの「Sign out」リンクが存在すること" do
      visit user_path(@user2)
      expect(page).to have_selector "#header_sign_out_link"
    end

  end

end

ダイレクトアクセスのテストと似ているところが多いですが、新出のコードを紹介していきます!

page

expect(page)という形で現れましたね。これは今表示されているページ全体のことです。

not_to

expect().not_toという表現がでてきましたね。
toの反対で()not_to以降がアンマッチであることを検証します。
マッチした場合にfalseになり、そのシナリオがFailureになります。

have_selector

have_selectorは指定したタグや属性を持っているかを検証します。
例えば、以下のような要素があるとします。

<h1 id="title" class="main-title">Title</h1>

これをタグ、id属性、class属性でそれぞれhave_selectorで検証しようとすると以下のようになります。

# タグで検証
expect(page).to have_selector("h1")

# id属性で検証
expect(page).to have_selector("#title")

# class属性で検証
expect(page).to have_selector(".main-title")

ページ内や子要素に特定の要素がないかを調べる時に使うので覚えておくとよしです!

sleep

sleepは指定した秒数、次のコードの実行を待つコードです。sleep 1であれば1秒待った後に次の行に進みます。
今回は、アプリケーション側でサインイン状態を確認した後リダイレクトする処理を入れていますが、自動化されたテストがそのままのスピードで検証を進めてしまうとリダイレクト処理が終わる前に検証が完了してしまい、思ったとおりの結果を得られないことがあります。
こういった自体を防ぐために、sleepを挟むことで処理を待たせることも必要になります。

ただし、sleepの使用は最低限にするべきです。なぜならそのせいでテストの実行時間が長くなってしまっては自動化した意味が失われかねないからです。

3. ユーザーとして、サインアップしたい

ファイル名は03_sign_up_spec.rbでいきます!

# touch spec/system/03_sign_up_spec.rb

そして、今回もidを仕込みます。

app/views/static_pages/home.html.erb
- <%= link_to "Sign up now!", sign_up_path, class: "btn btn-lg btn-primary mt-5" %>
+ <%= link_to "Sign up now!", sign_up_path, class: "btn btn-lg btn-primary mt-5", id: :sign_up_link %>
app/views/users/new.html.erb
  <%= form_with model: @user, url: create_user_path, local: true do |form| %>
    ...
    <div class="form-group mt-5">
-     <%= form.submit "Sign up!", class: "form-control btn btn-primary" %>
+     <%= form.submit "Sign up!", class: "form-control btn btn-primary", id: :sign_up_button %>
    </div>
  <% end %>
- <p class="text-center">登録済みの方は<%= link_to "こちら", sign_in_path %></p>
+ <p class="text-center">登録済みの方は<%= link_to "こちら", sign_in_path, id: :sign_in_link %></p>

はい。ではテストコードです。

spec/system/03_sign_up_spec.rb
feature "ユーザーとして、サインアップしたい", type: :system do
  background do
    @user = User.new(name: "John Smith", email: "john@sample.com", password: "john1234")
  end

  scenario "未サインインのユーザーが、トップページで「Sign up now!」ボタンを選択したとき、サインアップページに遷移すること" do
    visit root_path
    click_on :sign_up_link
    expect(current_path).to eq sign_up_path
  end

  scenario "サインアップページで「お名前」を入力できること" do
    visit sign_up_path
    fill_in :user_name, with: @user.name
    expect(find("#user_name").value).to eq @user.name
  end

  scenario "サインアップページで「メールアドレス」を入力できること" do
    visit sign_up_path
    fill_in :user_email, with: @user.email
    expect(find("#user_email").value).to eq @user.email
  end

  scenario "サインアップページで「パスワード」を入力できること" do
    visit sign_up_path
    fill_in :user_password, with: @user.password
    expect(find("#user_password").value).to eq @user.password
  end

  scenario "サインアップページで「パスワード」はマスク化されること" do
    visit sign_up_path
    fill_in :user_password, with: @user.password
    expect(find("#user_password")[:type]).to eq "password"
  end

  scenario "サインアップページで「パスワード」を入力したユーザーが、「パスワードを表示する」チェックボックスにチェックをいれたとき、「パスワード」が表示されること" do
    visit sign_up_path
    fill_in :user_password, with: @user.password
    check :visible_password
    expect(find("#user_password")[:type]).to eq "text"
  end

  scenario "サインアップページで「パスワード」を入力したユーザーが、「パスワードを表示する」チェックボックスのチェックを外したとき、「パスワード」がマスク化されること" do
    visit sign_up_path
    fill_in :user_password, with: @user.password
    check :visible_password
    uncheck :visible_password
    expect(find("#user_password")[:type]).to eq "password"
  end

  scenario "サインアップページで「お名前」を入力していないユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「お名前」未入力のエラーメッセージが表示されること" do
    error_message = "お名前を入力してください"

    visit sign_up_path
    fill_in :user_name, with: ""
    fill_in :user_email, with: @user.email
    fill_in :user_password, with: @user.password
    click_on :sign_up_button
    
    expect(current_path).to eq sign_up_path
    expect(page).to have_text error_message
  end

  scenario "サインアップページで「お名前」を51文字以上入力したユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「お名前」文字数超過のエラーメッセージが表示されること" do
    error_message = "お名前は50文字以内で入力してください"

    visit sign_up_path
    fill_in :user_name, with: "a" * 51
    fill_in :user_email, with: @user.email
    fill_in :user_password, with: @user.password
    click_on :sign_up_button

    expect(current_path).to eq sign_up_path
    expect(page).to have_text error_message

    fill_in :user_name, with: "a" * 50
    fill_in :user_password, with: @user.password
    click_on :sign_up_button

    expect(current_path).not_to eq sign_up_path
    expect(page).not_to have_text error_message
    expect(current_path).to eq user_path(User.find_by(email: @user.email))
  end

  scenario "サインアップページで「メールアドレス」を入力していないユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「メールアドレス」未入力のエラーメッセージが表示されること" do
    error_message = "メールアドレスを入力してください"

    visit sign_up_path
    fill_in :user_name, with: @user.name
    fill_in :user_email, with: ""
    fill_in :user_password, with: @user.password
    click_on :sign_up_button

    expect(current_path).to eq sign_up_path
    expect(page).to have_text error_message
  end

  scenario "サインアップページで「メールアドレス」を256文字以上入力したユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「メールアドレス」文字数超過のエラーメッセージが表示されること" do
    error_message = "メールアドレスは255文字以内で入力してください"

    visit sign_up_path
    fill_in :user_name, with: @user.name
    fill_in :user_email, with: "a" * 245 + "@sample.com"
    fill_in :user_password, with: @user.password
    click_on :sign_up_button

    expect(current_path).to eq sign_up_path
    expect(page).to have_text error_message

    fill_in :user_email, with: "a" * 244 + "@sample.com"
    fill_in :user_password, with: @user.password
    click_on :sign_up_button
    
    expect(current_path).not_to eq sign_up_path
    expect(page).not_to have_text error_message
    expect(current_path).to eq user_path(User.find_by(email: "a" * 244 + "@sample.com")) 
  end

  scenario "サインアップページで「メールアドレス」を誤ったフォーマットで入力したユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「メールアドレス」フォーマットチェックエラーのエラーメッセージが表示されること" do
    error_message = "メールアドレスは不正な値です"

    visit sign_up_path
    fill_in :user_name, with: @user.name
    fill_in :user_email, with: "sample.com"
    fill_in :user_password, with: @user.password
    click_on :sign_up_button

    expect(current_path).to eq sign_up_path
    expect(page).to have_text error_message
  end

  scenario "サインアップページで「メールアドレス」がすでに登録済みのメールアドレスを入力したユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「メールアドレス」重複のエラーメッセージが表示されること" do
    error_message = "メールアドレスはすでに存在します"
    @user.save

    visit sign_up_path
    fill_in :user_name, with: @user.name
    fill_in :user_email, with: @user.email.upcase
    fill_in :user_password, with: @user.password
    click_on :sign_up_button

    expect(current_path).to eq sign_up_path
    expect(page).to have_text error_message
  end

  scenario "サインアップページで「パスワード」を入力していないユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「パスワード」文字数不足のエラーメッセージが表示されること" do
    error_message = "パスワードは6文字以上で入力してください"

    visit sign_up_path
    fill_in :user_name, with: @user.name
    fill_in :user_email, with: @user.email
    fill_in :user_password, with: ""
    click_on :sign_up_button

    expect(current_path).to eq sign_up_path
    expect(page).to have_text error_message
  end

  scenario "サインアップページで「パスワード」を5文字以下で入力したユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは失敗し「パスワード」文字数不足のエラーメッセージが表示されること" do
    error_message = "パスワードは6文字以上で入力してください"

    visit sign_up_path
    fill_in :user_name, with: @user.name
    fill_in :user_email, with: @user.email
    fill_in :user_password, with: "john1"
    click_on :sign_up_button

    expect(current_path).to eq sign_up_path
    expect(page).to have_text error_message

    fill_in :user_password, with: "john12"
    click_on :sign_up_button

    expect(current_path).not_to eq sign_up_path
    expect(page).not_to have_text error_message
    expect(current_path).to eq user_path(User.find_by(email: @user.email))
  end

  feature nil, type: :system do
    background do
      @welcome_message = "サインアップありがとう"

      visit sign_up_path
      fill_in :user_name, with: @user.name
      fill_in :user_email, with: @user.email
      fill_in :user_password, with: @user.password
      click_on :sign_up_button
    end

    scenario "サインアップページで「お名前」「メールアドレス」「パスワード」を正しく入力したユーザーが、「Sign up!」ボタンをクリックしたとき、サインアップは成功し、そのユーザーのユーザー詳細ページにサインイン済状態で遷移すること" do
      expect(current_path).to eq user_path(User.find_by(email: @user.email))
      expect(page).not_to have_selector "#header_sign_in_link"
      expect(page).to have_selector "#header_sign_out_link"
    end

    scenario "サインアップに成功したユーザーは、遷移後のユーザー詳細ページで自分の入力した「お名前」を確認できること" do
      expect(page).to have_text @user.name
    end

    scenario "サインアップに成功したユーザーは、遷移後のユーザー詳細ページで自分の入力した「メールアドレス」を確認できること" do
      expect(page).to have_text @user.email
    end

    scenario "サインアップに成功したユーザーは、遷移後のユーザー詳細ページでウェルカムメッセージを確認できること" do
      expect(page).to have_text @welcome_message
    end

    scenario "サインアップに成功したユーザーは、遷移後のユーザー詳細ページをリロードしたとき、ウェルカムメッセージを確認できなくなること" do
      visit current_path
      expect(page).not_to have_text @welcome_message
    end
  end

  scenario "サインアップページで「登録済みの方はこちら」リンクを選択したとき、サインインページに遷移すること" do
    visit sign_up_path
    click_on :sign_in_link
    expect(current_path).to eq sign_in_path
  end
end

また、はじめましての書き方を紹介していきます。

find

モデルのときにつかったfindとはまた別ですよ。
find()でページの中から()内で指定した要素を取得してくれます。1つ以上該当するものがあるとエラーになってしまうので、id属性に対して使うのが好ましいでしょう。
例えば今回のテストコードでは、

expect(find("#user_name").value).to eq @user.name

のように使っていますが、これでid属性がuser_nameに定義されている要素を取得します。

value

find("#user_name").value

のようにvalueを使っています。これはinputvalue属性を取得しています。
value属性にはinput type="text"などの場合にはテキストボックスにデフォルトで入力しておきたい文字列を入力しておいたりしますが、Capybaraではvalue属性を取得することで今入力されている文字列を取得することができます。

[:type]

find("#user_password")[:type]

のように使っています。Capybaraではvaluetextは要素と.でつなぐことで取得できるのですが、それ以外の属性は[:attribute_name]の形式で取得します。[:type]だとtype属性を取得してきていることになりますね。
今回はpasswordtype属性をjavascriptでtextpasswordを切り替えているので、これでチェックができます。textはマスク化なし、passwordはマスク化ありはHTML5の仕様なので、今回のテストではtype属性が正しく指定されているかを検証しました。

check

checkはチェックボックスにチェックする操作です。
今回は

check :visible_password

の形式でid属性がvisible_passwordのチェックボックスにチェックを入れています。

uncheck

uncheckcheckの反対でチェックボックスからチェックを外す操作です。

have_text

have_textは指定した文字列が存在するかどうかを検証するために使います。

expect(page).to have_text xxxxxxxxxx

と記述することでページのどこかにでもxxxxxxxxxxの文字列が存在しないかを検証します。
pageの箇所をfind()などで限定した要素にすることで、その要素内にxxxxxxxxxxの文字列が存在するかどうかを検証するように範囲を狭めることもできます。

visit current_path

以前お話したようにcurrent_pathは現在のパスです。そこにvisitしているということは...

そう!これはリロード操作ですね。

はい。今回のテストコードで新しく出てきた表現はこのくらいではないでしょうか。
そろそろ慣れてきましたか?一回書き始めると案外それらの組み合わせだけでいろいろなテストを実行できることがわかってきたんじゃないかと思います。
それでは次はサインインのテストコードを記述していきましょう!

4. ユーザーとして、サインインしたい

まずはシナリオファイルの作成から。

# touch spec/system/04_sign_in_spec.rb

そして、必要な箇所にid属性を振ります。

app/views/sessions/new.html.erb
- <p class="text-center">登録がまだの方は<%= link_to "こちら", sign_up_path %></p>
+ <p class="text-center">登録がまだの方は<%= link_to "こちら", sign_up_path, id: :sign_up_link %></p>

以下、テストコードです。今回は目新しい表現はないので、下のコードを見ずに書いてみてもらっても面白いかもしれないです。

spec/system/04_sign_in_spec.rb
feature "ユーザーとして、サインインしたい", type: :system do
  background do
    @user = User.create(name: "John Smith", email: "john@sample.com", password: "john1234")
  end

  scenario "サインインページで「メールアドレス」を入力できること" do
    visit sign_in_path
    fill_in :user_email, with: @user.email
    expect(find("#user_email").value).to eq @user.email
  end

  scenario "サインインページで「パスワード」を入力できること" do
    visit sign_in_path
    fill_in :user_password, with: @user.password
    expect(find("#user_password").value).to eq @user.password
  end

  scenario "サインインページで「パスワード」はマスク化されること" do
    visit sign_in_path
    fill_in :user_password, with: @user.password
    expect(find("#user_password")[:type]).to eq "password"
  end

  scenario "サインインページで「パスワード」を入力したユーザーが、「パスワードを表示する」チェックボックスにチェックをいれたとき、「パスワード」が表示されること" do
    visit sign_in_path
    fill_in :user_password, with: @user.password
    check :visible_password
    expect(find("#user_password")[:type]).to eq "text"
  end

  scenario "サインインページで「パスワード」を入力したユーザーが、「パスワードを表示する」チェックボックスのチェックを外したとき、「パスワード」がマスク化されること" do
    visit sign_in_path
    fill_in :user_password, with: @user.password
    check :visible_password
    uncheck :visible_password
    expect(find("#user_password")[:type]).to eq "password"
  end

  scenario "サインインページで「メールアドレス」を入力していないユーザーが、「Sign in」ボタンをクリックしたとき、サインイン失敗のエラーメッセージが表示されること" do
    error_message = "メールアドレスまたはパスワードをもう一度確認してください。"

    visit sign_in_path
    fill_in :user_email, with: ""
    fill_in :user_password, with: @user.password
    click_on :sign_in_button

    expect(current_path).to eq sign_in_path
    expect(page).to have_text error_message
  end

  scenario "サインインページで「メールアドレス」として登録されていないメールアドレスを入力したユーザーが、「Sign in」ボタンをクリックしたとき、サインイン失敗のエラーメッセージが表示されること" do
    error_message = "メールアドレスまたはパスワードをもう一度確認してください。"

    visit sign_in_path
    fill_in :user_email, with: "dummy@sample.com"
    fill_in :user_password, with: @user.password
    click_on :sign_in_button

    expect(current_path).to eq sign_in_path
    expect(page).to have_text error_message
  end

  scenario "サインインページで「メールアドレス」は正しいが「パスワード」を入力していないユーザーが、「Sign in」ボタンをクリックしたとき、サインイン失敗のエラーメッセージが表示されること" do
    error_message = "メールアドレスまたはパスワードをもう一度確認してください。"

    visit sign_in_path
    fill_in :user_email, with: @user.email
    fill_in :user_password, with: ""
    click_on :sign_in_button

    expect(current_path).to eq sign_in_path
    expect(page).to have_text error_message  
  end

  scenario "サインインページで「メールアドレス」は正しいが「パスワード」が正しくないユーザーが、「Sign in」ボタンをクリックしたとき、サインイン失敗のエラーメッセージが表示されること" do
    error_message = "メールアドレスまたはパスワードをもう一度確認してください。"

    visit sign_in_path
    fill_in :user_email, with: @user.email
    fill_in :user_password, with: @user.password + "a"
    click_on :sign_in_button

    expect(current_path).to eq sign_in_path
    expect(page).to have_text error_message      
  end

  feature nil, type: :system do
    background do
      @sign_in_message = "サインインしました。"

      visit sign_in_path
      fill_in :user_email, with: @user.email
      fill_in :user_password, with: @user.password
      click_on :sign_in_button        
    end
    
    scenario "サインインページで「メールアドレス」「パスワード」に正しい値を入力したユーザーが、「Sign in」ボタンをクリックしたとき、サインイン済状態でそのユーザーのユーザー詳細ページに遷移すること" do
      expect(current_path).to eq user_path(@user)
      expect(page).not_to have_selector "#header_sign_in_link"
      expect(page).to have_selector "#header_sign_out_link"
    end
  
    scenario "サインインに成功したユーザーは、遷移後のユーザー詳細ページでサインイン成功メッセージを確認できること" do
      expect(page).to have_text @sign_in_message
    end

    scenario "サインインに成功したユーザーは、遷移後のユーザー詳細ページをリロードしたとき、サインイン成功メッセージを確認できなくなること" do
      visit current_path
      expect(page).not_to have_text @sign_in_message
    end
  end

  scenario "サインインページで「登録がまだの方はこちら」リンクを選択したとき、サインアップページに遷移すること" do
    visit sign_in_path
    click_on :sign_up_link

    expect(current_path).to eq sign_up_path
  end
end

5. ユーザーとして、サインアウトしたい

次はサインアウトについてですね。

# touch spec/system/05_sign_out_spec.rb

そしてコーディング。これも今までのコードの組み合わせで表現可能ですね。

spec/system/05_sign_out_spec.rb
feature "ユーザーとして、サインアウトしたい", type: :system do
  scenario "サインイン済のユーザーが、ユーザー詳細ページでヘッダーの「Sign out」リンクをクリックしたとき、未サインイン状態になりトップページに遷移すること" do
    user = User.create(name: "John Smith", email: "john@sample.com", password: "john1234")

    visit sign_in_path
    fill_in :user_email, with: user.email
    fill_in :user_password, with: user.password
    click_on :sign_in_button

    visit user_path(user)

    click_on :header_sign_out_link

    expect(current_path).to eq root_path
    expect(page).to have_selector "#header_sign_in_link"
    expect(page).not_to have_selector "#header_sign_out_link"
  end
end

コメントアウトなどで説明文を書いたりは省いていますが、今までの内容が理解できていれば何をしているのか想像できると思います。
サインインページにアクセスしてサインインし、ユーザー詳細ページでヘッダーのサインアウトリンクをクリックし、トップページにリダイレクトされたと同時に未サインイン状態になっていることを検証していますね。

6. ユーザーとして、他のユーザーの情報を閲覧したい

まずはシナリオファイルです。

# touch spec/system/06_show_user_info_spec.rb

今回のテストでは、NotFoundのユーザーのユーザー詳細ページを表示しようとした時に、NotFoundのページが表示される、という項目があります。
Railsでは、NotFoundの例外が発生した場合、production環境の場合はデフォルトでNotFound用のページが表示されるようになっています。
test環境でも同じようにNotFoundページが表示されるようにconfigを変更します。

config/environments/test.rb
- config.consider_all_requests_local       = true
+ config.consider_all_requests_local       = false
- config.action_dispatch.show_exceptions = false
+ config.action_dispatch.show_exceptions = true

これで準備完了です。
NotFoundの場合、public/404.htmlが表示されるようになります。
中身を見ると、

<h1>The page you were looking for doesn't exist.</h1>

と記述されているので、この文字列があるかどうかをチェックするようにします。

ではテストコードです。

spec/system/06_show_user_info_spec.rb
feature "ユーザーとして、他のユーザーの情報を閲覧したい", type: :system do
  background do
    @user1 = User.create(name: "John Smith", email: "john@sample.com", password: "john1234")
    @user2 = User.create(name: "Taro Yamada", email: "taro@sample.com", password: "taro1234")    
  end
  
  scenario "ユーザーが、存在するユーザーのユーザー詳細ページにアクセスしようとしたとき、そのユーザーの「お名前」「メールアドレス」を確認できること" do

    # Before sign in
    visit user_path(@user1)
    expect(page).to have_text @user1.name
    expect(page).to have_text @user1.email
    expect(page).not_to have_text @user2.name
    expect(page).not_to have_text @user2.email

    visit user_path(@user2)
    expect(page).not_to have_text @user1.name
    expect(page).not_to have_text @user1.email
    expect(page).to have_text @user2.name
    expect(page).to have_text @user2.email

    # After sign in
    visit sign_in_path
    fill_in :user_email, with: @user1.email
    fill_in :user_password, with: @user1.password
    click_on :sign_in_button

    visit user_path(@user1)
    expect(page).to have_text @user1.name
    expect(page).to have_text @user1.email
    expect(page).not_to have_text @user2.name
    expect(page).not_to have_text @user2.email

    visit user_path(@user2)
    expect(page).not_to have_text @user1.name
    expect(page).not_to have_text @user1.email
    expect(page).to have_text @user2.name
    expect(page).to have_text @user2.email
  end

  scenario "ユーザーが、存在しないユーザーのユーザー詳細ページにアクセスしようとしたとき、エラーが発生すること" do
    not_found_message = "The page you were looking for doesn't exist."
    not_found_id = @user2.id + 1
    expect{User.find(not_found_id)}.to raise_exception(ActiveRecord::RecordNotFound)

    # Before sign in
    visit user_path(not_found_id)
    expect(page).to have_text not_found_message

    # After sign in
    visit sign_in_path
    fill_in :user_email, with: @user1.email
    fill_in :user_password, with: @user1.password
    click_on :sign_in_button

    visit user_path(not_found_id)
    expect(page).to have_text not_found_message
  end
end

基本的には今までと変わりありませんね。
一つだけexceptionの検証の仕方だけ新出があるのでそれの説明を。

expect{}.to raise_exception()

今までと違うのはexpectの検証ターゲットを()ではなく{}でかこっていることですね。
そしてraise_exceptionの後に期待する例外を記述します。
今回は@user2id+1したidのユーザーを検索しています。idはシーケンシャルに払い出されるので@user2よりも大きいidを持っているユーザーはいないはず。
なのでUser.find(not_found_id)ActiveRecord::RecordNotFoundの例外が発生するはずです。

はい。ここまでで今のところ考えられる全てのテストケースをコーディングしてみました。
テストを実行してみましょう!

# rspec
Capybara starting Puma...
* Version 4.3.1 , codename: Mysterious Traveller
* Min threads: 0, max threads: 4
* Listening on tcp://127.0.0.1:46237
......................................................................

Finished in 1 minute 21.27 seconds (files took 5.82 seconds to load)
70 examples, 0 failures

全てパスしてますね!
もしパスしないテストケースがある場合は、もう一度アプリのコードかテストコードを見返してみてくださいね。

また、あえてエラーになるようにテストコードを書き直してみてエラーになることを確認してみるのも面白いと思います。

さて、では本日はここまでにしましょう!

まとめ

今日は今まで作ってきたアプリに対してテストコードをコーディングしてみました。
これによって今後リファクタリングの都度自動テストを回すだけで全ての動作を確認することができるようになりましたね。

テスト自動化、楽しいですよね??

テスト自動化は新しい機能を作ったり、アプリの仕様自体を変更する時にテストコードも記述する必要があるのでその分稼働が必要になることもあります。
しかし、リファクタリングや新機能開発時のデグレテストを簡略化でき、自動テストをパスしていればデプロイを自信をもって行える安心感を得ることができます。特にアプリが大きくなっていくと、これは稼働以上に嬉しい恩恵です。

今後はこのハンズオンでもTDDで開発を進めていきます!

では、次回も乞うご期待!ここまでお読みいただきありがとうございました!

Next: Coming Soon

後片付け

では後片付けしていきますー。

前回もお話した通り、RSpecのシステムテストはテストが終わるとDBを勝手にリセットしてくれます。
ので、コンテナを落として終了ですね。

# exit
$ docker-compose down

本日のソースコード

Reference

Other Hands-on Links

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?