Railsチュートリアル 第13章 ユーザーのマイクロポスト - 「プロフィール画面へのマイクロポストの表示」を、テスト駆動で実装していく

# テストを実装するための前提

「ユーザーのプロフィール画面に、当該ユーザーのマイクロポストを表示する機能の実装」についてのテストは、実際にテストを実装していく前に必要となる準備がいくつかあります。

## プロフィール画面用の統合テストを生成する

まずはプロフィール画面用の統合テストを生成することから始まります。`rails generate integration_test`コマンドですね。

```
# rails generate integration_test users_profile
Running via Spring preloader in process 1793
      invoke  test_unit
      create    test/integration/users_profile_test.rb
```

## マイクロポストのfixtureをユーザーと関連付ける

各マイクロポストの`:user`という属性に、有効なユーザー名を渡します。

```diff:test/fixtures/microposts.yml
  most_recent:
    content: "Writing a short test"
    created_at: <%= Time.zone.now %>
+   user: rhakurei
```

「有効なユーザー名」というのは、`test/fixtures/users.yml`に存在するユーザー名を意味します。

```yaml:test/fixtures/users.yml
rhakurei:
  name: Reimu Hakurei
  email: rhakurei@example.com
  # ...略
```

## 多数のマイクロポストを生成する

マイクロポストに対するページネーション機能のテストを行うためには、ある程度まとまった数（今回であれば30数個以上）のマイクロポストを準備する必要があります。その内容をいちいち考えて書くのは無駄な労力といえます。

幸いにして、我々はFaker gemを用いてマイクロポストの本文内容を自動生成することが可能です。また、Railsの機能を使えば、fixture中で多数のマイクロポストを効率よく生成することも可能です。

Railsのfixtureを前提とした場合、多数のマイクロポストを生成するには、fixture中で埋め込みRubyを用いると便利です。

```yaml:
<% 30.times do |n| %>
micropost_<%= n %>:
  content: <%= Faker::Lorem.sentence(5) %>
  created_at: <%= 42.days.ago %>
  user: rhakurei
<% end %>
```

この例では、`Faker::Lorem.sentence`メソッドによってランダムな文字列を生成し、その内容をマイクロポストの内容としています。

# 「ユーザーのプロフィール画面に、当該ユーザーのマイクロポストを表示する機能の実装」についてのテストの内容

上記準備を終えれば、テストそのものの実装に移ることができます。

`test/integration/users_profile_test.rb`の内容は以下のようになります。テストの名前は「profile display」としています。

```ruby:test/integration/users_profile_test.rb
require 'test_helper'

class UsersProfileTest < ActionDispatch::IntegrationTest
  include ApplicationHelper

  def setup
    @user = users(:rhakurei)
  end

  test "profile display" do
    get user_path(@user)
    assert_template 'users/show'
    assert_select 'title', full_title(@user.name)
    assert_select 'h1', text: @user.name
    assert_select 'h1>img.gravatar'
    assert_match @user.microposts.count.to_s, response.body
    assert_select 'div.pagination'
    @user.microposts.paginate(page: 1).each do |micropost|
      assert_match micropost.content, response.body
    end
  end
end
```

上記テストコードの重要なポイントは以下です。

* `ApplicationHelper`を`include`することにより、`ApplicationHelper#full_title`メソッドが利用可能なようにしている
* 生成されたHTMLにマイクロポストの数が出力されているかどうかの確認に`response.body`を用いている
    * `response.body`には、HTMLのbody要素のみならず、完全なHTMLが含まれている
* `assert_match`を用いれば、「HTMLのどこかに存在する」というようなテストが可能である
    * 「どの要素に含まれているか」は関係ない、というのがポイント
* `h1>img.gravatar`というのは、「`h1`要素の内側にある`gravatar`クラス付きの`img`要素」を意味する
    * こうした入れ子関係に対してのテストも可能である

# テスト駆動で実装を追加していく

テストが実装できたので、今度はテストに対応する実装を追加していきましょう。

## 「プロフィール画面のビューにマイクロポストの表示を実装する」前の段階でのテストの結果

「プロフィール画面のビューにマイクロポストの表示を実装する」前の段階で当該テストを実行した結果は以下になります。

```
# rails test test/integration/users_profile_test.rb
Running via Spring preloader in process 1825
Started with run options --seed 15695

 FAIL["test_profile_display", UsersProfileTest, 2.75337920000311]
 test_profile_display#UsersProfileTest (2.75s)
        Expected /34/ to match ...略.
        test/integration/users_profile_test.rb:16:in `block in <class:UsersProfileTest>'

  1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.76194s
1 tests, 6 assertions, 1 failures, 0 errors, 0 skips
```

「...略」の部分には、`response.body`の内容全体が出力されます。

`test/integration/users_profile_test.rb`の16行目は、現時点の私の環境では以下のコードが記述されています。

```ruby:test/integration/users_profile_test.rb（16行目）
assert_match @user.microposts.count.to_s, response.body
```

この内容ということは、「マイクロポストの投稿数が表示されていない」という旨のメッセージですね。

## ユーザーの`show`ページに、「マイクロポストの投稿数表示」を実装する

`app/views/users/show.html.erb`の対応する実装は以下です。

```diff:app/views/users/show.html.erb
  <% provide(:title, @user.name) %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <h1>
          <%= gravatar_for @user %>
          <%= @user.name %>
        </h1>
      </section>
    </aside>
+   <div class="col-md-8">
+     <% if @user.microposts.any? %>
+       <h3>Microposts (<%= @user.microposts.count %>)</h3>
+     <% end %>
+   </div>
  </div>
```

`if @user.microposts.any?`というのは、「ユーザーのマイクロポストが一つも存在しない場合、空のリストを表示させることはしない」という趣旨を表します。

## 「マイクロポストの投稿数表示」の実装を終えた時点でのテストの結果

```
# rails test test/integration/users_profile_test.rb
Running via Spring preloader in process 1838
Started with run options --seed 39528

 FAIL["test_profile_display", UsersProfileTest, 2.8101306000025943]
 test_profile_display#UsersProfileTest (2.81s)
        Expected at least 1 element matching "div.pagination", found 0..
        Expected 0 to be >= 1.
        test/integration/users_profile_test.rb:17:in `block in <class:UsersProfileTest>'

  1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.82150s
1 tests, 7 assertions, 1 failures, 0 errors, 0 skips
```

「`pagination`クラスを持つ`div`タグがない」という趣旨のメッセージが表示されて、テストが失敗しています。

`test/integration/users_profile_test.rb`の17行目は、現時点の私の環境では以下のコードが記述されています。

```ruby:test/integration/users_profile_test.rb（17行目）
assert_select 'div.pagination'
```

確かに「`pagination`クラスを持つ`div`タグが必要である」ということですね。

## ユーザーの`show`ページに、`will_paginate`メソッドを追加する

`app/views/users/show.html.erb`の対応する実装は以下です。

```diff:app/views/users/show.html.erb
  <% provide(:title, @user.name) %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <h1>
          <%= gravatar_for @user %>
          <%= @user.name %>
        </h1>
      </section>
    </aside>
    <div class="col-md-8">
      <% if @user.microposts.any? %>
        <h3>Microposts (<%= @user.microposts.count %>)</h3>
+       <%= will_paginate @microposts %>
      <% end %>
    </div>
  </div>
```

これで`pagination`クラスを持つ`div`タグが描画されるようになります。

## 「ユーザーの`show`ページへの`will_paginate`メソッドの追加」を終えた時点でのテストの結果

```
# rails test test/integration/users_profile_test.rb
Running via Spring preloader in process 1851
Started with run options --seed 39965

 FAIL["test_profile_display", UsersProfileTest, 2.5906765999970958]
 test_profile_display#UsersProfileTest (2.59s)
        Expected /Writing\ a\ short\ test/ to match "...略".
        test/integration/users_profile_test.rb:19:in `block (2 levels) in <class:UsersProfileTest>'
        test/integration/users_profile_test.rb:18:in `block in <class:UsersProfileTest>'

  1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.59613s
1 tests, 9 assertions, 1 failures, 0 errors, 0 skips
```

「...略」の部分には、`response.body`の内容全体が出力されます。

`test/integration/users_profile_test.rb`の18〜20行目は、現時点の私の環境では以下のコードが記述されています。

```ruby:test/integration/users_profile_test.rb（18〜20行目）
@user.microposts.paginate(page: 1).each do |micropost|
  assert_match micropost.content, response.body
end
```

「マイクロポストの内容がHTML中に見当たらない」という趣旨のメッセージのようです。

## ユーザーの`show`ページに、「マイクロポストの投稿数表示」を実装する

`app/views/users/show.html.erb`の対応する実装は以下です。

```diff:app/views/users/show.html.erb
  <% provide(:title, @user.name) %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <h1>
          <%= gravatar_for @user %>
          <%= @user.name %>
        </h1>
      </section>
    </aside>
    <div class="col-md-8">
      <% if @user.microposts.any? %>
        <h3>Microposts (<%= @user.microposts.count %>)</h3>
+       <ol class="microposts">
+         <%= render @microposts %>
+       </ol>
        <%= will_paginate @microposts %>
      <% end %>
    </div>
  </div>
```

## 「マイクロポストの投稿数表示」の実装を終えた時点でのテストの結果

```
# rails test test/integration/users_profile_test.rb
Running via Spring preloader in process 1864
Started with run options --seed 47117

  1/1: [===================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.79243s
1 tests, 67 assertions, 0 failures, 0 errors, 0 skips
```

この時点で、`test/integration/users_profile_test.rb`を対象としたテストが成功するようになりました。

テストスイート全体を対象としたテストも実施してみましょう。

```
# rails test
Running via Spring preloader in process 1877
Started with run options --seed 52362

  56/56: [=================================] 100% Time: 00:00:09, Time: 00:00:09

Finished in 9.37355s
56 tests, 302 assertions, 0 failures, 0 errors, 0 skips
```

テストスイート全体を対象としたテストも無事成功しましたね。