Help us understand the problem. What is going on with this article?

Railsチュートリアル 第13章 ユーザーのマイクロポスト - 画像アップローダーのテストに存在する一つの不具合と、その解消策

画像アップローダーのテスト(オリジナル)

Railsチュートリアル本文においては、第13章の演習 - 基本的な画像アップロードに登場するテストです。

同演習でFILL_INとなっている箇所を埋めると、テストの内容は以下のようになります。

test/integration/microposts_interface_test.rb
require 'test_helper'

class MicropostsInterfaceTest < ActionDispatch::IntegrationTest
  def setup
    @user = users(:rhakurei)
    @other_user = users(:skomeiji)
  end

  test "micropost interface" do
    log_in_as(@user)
    get root_path
    assert_select 'img.gravatar'
    assert_select 'h1', @user.name
    assert_select 'span', /#{@user.microposts.count}/
    assert_select 'form[action="/microposts"]'
    assert_select 'textarea'
    assert_select 'div.pagination'
    assert_select 'input[type="file"]'
    # 無効な送信
    assert_no_difference 'Micropost.count' do
      post microposts_path, params: { micropost: { content: "" } }
    end
    assert_select 'div#error_explanation'
    # 有効な送信
    content = "This micropost really ties the room together"
    picture = fixture_file_upload('test/fixtures/lgtm3.png', 'image/png')
    assert_difference 'Micropost.count', 1 do
      post microposts_path, params: { micropost: { content: content, picture: picture } }
    end
    assert assigns(:micropost).picture?
    assert_redirected_to root_url
    follow_redirect!
    assert_match content, response.body
    # 投稿を削除する
    assert_select 'a', text: 'delete'
    first_micropost = @user.microposts.paginate(page: 1).first
    assert_difference 'Micropost.count', -1 do
      delete micropost_path(first_micropost)
    end
    # 違うユーザーのプロフィールにアクセス(削除リンクがないことの確認)
    get user_path(users(:mkirisame))
    assert_select 'a', text: 'delete', count: 0
  end

  # ...略
end

このテストに関する一つの不具合

実は、上記のテストには一つ不具合があります。

例えば、以下のようにapp/views/microposts/_micropost.html.erbから画像表示部分の実装を取り除くと、テストの結果はどうなるでしょうか。

app/views/microposts/_micropost.html.erb
  <li id="micropost-<%= micropost.id %>">
    <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
    <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
    <span class="content">
      <%= micropost.content %>
-     <%= image_tag micropost.picture.url if micropost.picture? %>
    </span>
    <span class="timestamp">
      Posted <%= time_ago_in_words(micropost.created_at) %> ago.
      <% if current_user?(micropost.user) %>
        <%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %>
      <% end %>
    </span>
  </li>

以下のようにテストが成功してしまうのです。

# rails test test/integration/microposts_interface_test.rb
Running via Spring preloader in process 15428
Started with run options --seed 30155

  2/2: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.32782s
2 tests, 23 assertions, 0 failures, 0 errors, 0 skips

「画像を投稿したのに、投稿した画像がフィードに表示されていない」というのは、テストで検出できるべきバグであり、それを検出できないのはテストの不具合です。早速、テストに存在する当該不具合を解消していきましょう。

画像アップローダーのテストに存在する不具合を解消する

test/integration/microposts_interface_test.rbを以下のように変更するのが一つの方法です(他にも方法はあるかもしれません)。

test/integration/microposts_interface_test.rb
  require 'test_helper'

  class MicropostsInterfaceTest < ActionDispatch::IntegrationTest
    def setup
      @user = users(:rhakurei)
      @other_user = users(:skomeiji)
    end

    test "micropost interface" do
      log_in_as(@user)
      get root_path
      ...略
      # 有効な送信
      content = "This micropost really ties the room together"
      picture = fixture_file_upload('test/fixtures/lgtm3.png', 'image/png')
      assert_difference 'Micropost.count', 1 do
        post microposts_path, params: { micropost: { content: content, picture: picture } }
      end
-     assert assigns(:micropost).picture?
      assert_redirected_to root_url
      follow_redirect!
      assert_match content, response.body
+     assert_select "img[src*='#{picture.original_filename}']"
      # 投稿を削除する
      ...略
    end

    ...略
  end

新たなテストのポイント

ポイントは以下のassert_selectです。

assert_select "img[src*='#{picture.original_filename}']"

picture.original_filenameというのは、「アップロードしたファイルのローカルにおけるファイル名」と考えてかまいません1。また、CSSセレクタで部分一致検索を行うためには、*=という演算子を用います。

結果、上記assert_selectは、「src属性に、アップロードしたファイルのローカルにおけるファイル名を含むimg要素が存在すること」をチェックします。

また、Rails5で非推奨とされているassignsを使わない形となり、Webアプリケーションのテストの書き方としてより望ましいとされる「Request Specなテストの書き方」に近づけることができます。

実装を変更したテストが、本当に正しい対象へのテストなのか

マイクロポストに関連付けられた画像を表示する部分が欠落したビューに対してテストを実行する

app/views/microposts/_micropost.html.erbの内容が以下のようになっている場合、テストの結果はどうなるでしょうか。

app/views/microposts/_micropost.html.erb(不具合あり)
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content">
    <%= micropost.content %>
  </span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %>
    <% end %>
  </span>
</li>
# rails test test/integration/microposts_interface_test.rb
Running via Spring preloader in process 15608
Started with run options --seed 24115

 FAIL["test_micropost_interface", MicropostsInterfaceTest, 4.238321199998609]
 test_micropost_interface#MicropostsInterfaceTest (4.24s)
        Expected at least 1 element matching "img[src*='lgtm3.png']", found 0..
        Expected 0 to be >= 1.
        test/integration/microposts_interface_test.rb:34:in `block in <class:MicropostsInterfaceTest>'

  2/2: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.51704s
2 tests, 20 assertions, 1 failures, 0 errors, 0 skips
Expected at least 1 element matching "img[src*='lgtm3.png']", found 0..
Expected 0 to be >= 1.
test/integration/microposts_interface_test.rb:34

以上のようなメッセージが出てテストが失敗しています。「lgtm3.pngという文字列を含むsrc属性を持つimg要素が、1つ以上なければならないのに、1つも存在しない」という趣旨のメッセージですね。

私の環境では、test/integration/microposts_interface_test.rbの34行目は以下内容になっています。

test/integration/microposts_interface_test.rb(34行目)
assert_select "img[src*='#{picture.original_filename}']"

確かに想定通りの形でテストが失敗しているといえます。

マイクロポストに関連付けられた画像を表示する部分を正しく実装したビューに対してテストを実行する

app/views/microposts/_micropost.html.erbの中身を、今度は以下のようにしてテストを実行してみましょう。

app/views/microposts/_micropost.html.erb(不具合修正後)
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content">
    <%= micropost.content %>
    <%= image_tag micropost.picture.url if micropost.picture? %>
  </span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %>
    <% end %>
  </span>
</li>
# rails test test/integration/microposts_interface_test.rb
Running via Spring preloader in process 15621
Started with run options --seed 58972

  2/2: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.61052s
2 tests, 23 assertions, 0 failures, 0 errors, 0 skips

今度こそテストが無事成功しました。

関連リンク

「CSSにおける部分一致検索」については、以下の記事を参考にさせていただきました。ありがとうございます。


  1. より厳密には、「CarrierWaveデフォルトの状態だと日本語の文字が"__"に置き換えられてしまう」等の制限があるようなのですが、本記事で扱う内容の範囲外となるため、そのあたりの説明は割愛します。 

rapidliner00
* 現在のところは、エンジニアに憧れる非エンジニア * エンジニア的な業務効率化・改善に興味あり
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした