rails4.2系にアップグレード中に嬉しい情報が。

~/code/kowabana[upgrade-to-rails4.2.1]% rake test:all
DEPRECATION WARNING: rake test:all is deprecated and will be removed in Rails 5. Use rake test to run all tests in test directory. (called from mon_synchronize at /usr/local/var/rbenv/versions/2.1.2/lib/ruby/2.1.0/monitor.rb:211)

$ rakeで済むところが$ rake test:allって書かなきゃいけないのクッソうざかったんですよね。デフォルトテストスイートの嬉しさ減的な。

CIの設定でも$ rakeで済んだら素敵なのにと思ってたんです。

例えばコメント投稿のテスト。

# test/integration/post_comment_to_comic_test.rb:
require 'test_helper'

class PostCommentToComicTest < ActionDispatch::IntegrationTest
  test 'Post a comment' do
    visit comic_path comics(:comic)
    within('#new_comment') do
      fill_in 'comment[body]', with: 'コメントのテスト'
    end
    click_button '規約に同意してコメントする'
    assert has_selector? '.content-comment__body-text',
      text: 'コメントのテスト'
  end
end

よくあるこういうのを実行したら下記のエラーが。

% ruby -Itest test/integration/post_comment_to_comic_test.rb -n PostCommentToComicTest#test_Post_a_comment
Run options: -n PostCommentToComicTest#test_Post_a_comment --seed 29141

# Running:

E

Finished in 5.497046s, 0.1819 runs/s, 0.0000 assertions/s.

  1) Error:
PostCommentToComicTest#test_Post_a_comment:
Capybara::Webkit::ClickFailed: Failed to click element /html/body/div[@id='content']/div[@id='comments']/div/div[1]/form[@id='new_comment']/div[@id='comment_action']/input because of overlapping element /html/body/div[@id='content']/nav/div/ul/li[2]/a/span at position 831, 1025; 
A screenshot of the page at the time of the failure has been written to /var/folders/nb/5jp6psyd7h19my68chb0svvh0000gn/T/click_failed_W60479.png
    test/integration/post_comment_to_comic_test.rb:23:in `block in '

1 runs, 0 assertions, 0 failures, 1 errors, 0 skips

クリックできなかったElementのxpathを表示してくれる。そして何故クリックできなかったのか、overlapしているElementのpositionとxpathも表示してくれる。更にscreenshotここに撮っておいたからってどこまで至れり尽くせりだよ。

この場合、要はこんな感じになっててコメントボタンがクリックできない。

こんな方法でいいのかどうかわかりませんが、テスト前にexecute_scriptでoverlapしてるメニューをhideして対処。

# test/integration/post_comment_to_comic_test.rb:
require 'test_helper'

class PostCommentToComicTest < ActionDispatch::IntegrationTest
  test 'Post a comment' do
    visit comic_path comics(:comic)
    execute_script("$('nav.other-pages-nav').hide()") # It's workaround.
    within('#new_comment') do
      fill_in 'comment[body]', with: 'コメントのテスト'
    end
    click_button '規約に同意してコメントする'
    assert has_selector? '.content-comment__body-text',
      text: 'コメントのテスト'
  end
end

更に背景と同色の文字問題とかもサポートしてくれたら手動QA不要とはいわないけどもっと自動テスト化できますね。

sass-railsの5.0から、foo.css.sassfoo.sassに変えよというWARNINGが出るようになってました。(エラーが出たらとりあえずGoogle検索欄に放り込む人=俺用のエントリータイトル)
DEPRECATION WARNING: Extra .css in SASS file is unnecessary. Rename /Users/komagata/code/kowabana/app/assets/stylesheets/blocks/footer/_footer-pages-nav.css.sass to /Users/komagata/code/kowabana/app/assets/stylesheets/blocks/footer/_footer-pages-nav.sass
...
...
...

デザイナーの作業とコンフリクトする予感!

% ls app/assets/stylesheets/**/*.css.sass | wc -l
     107

すげーたくさんあってやんなりますよぉ~

% zmv app/assets/stylesheets/**/*.css.sass app/assets/stylesheets/**/*.sass
% zmv app/assets/stylesheets/*.css.sass app/assets/stylesheets/*.sass
% git add 'app/assets/stylesheets/**/*.sass'
% git add 'app/assets/stylesheets/*.sass'
% git rm 'app/assets/stylesheets/**/*.css.sass'
% git rm 'app/assets/stylesheets/*.css.sass'

zmv使うとちょっと楽。

参照:zsh の zmv を使って簡単に複数ファイルを一括リネームする - mollifier delta blog

こちらの記事の解決編です。

ActiveRecord::RecordNotFoundのエラーは放置すべきか? - komagata

@hiroshi3110さんからズバリな答えをいただきました。

怖話で実装

# lib/record_not_found_by_trustless_param.rb:
class RecordNotFoundByTrustlessParam < StandardError; end
# app/controllers/comics_controller.rb:
class ComicsController < ApplicationController
  before_action :set_comic, only: :show
  
  def show
  end

  private
  def set_comic
    unless @comic = Comic.find_by(id: params[:id])
      raise RecordNotFoundByTrustlessParam                                                                                                                                                                  
    end
  end
end
# app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
  rescue_from RecordNotFoundByTrustlessParam, with: :not_found
  
  private
  def not_found
    render file: "#{Rails.root}/public/404.html", layout: false, status: 404
  end
end

@hiroshi3110さんの言うとおり、信頼出来ない外部からのidを元にしたfindで見つからない場合はRecordNotFoundByTrustlessParamにし、rescue_fromで拾って404を出すようにしました。(404を動的テンプレートで出すのは良くないと思うのでpublic以下の静的ファイルを読む)

自分の中のベストプラクティスにしようと思いました。

Rollbarとかのエラー管理サービス使ってるとActiveRecord::RecordNotFound がWARNINGレベルのエラーでたくさん残るので気になる。

idに適当な数字入れれば必ず出るんだから。rollbarの無料プランは月のエラー数に上限があるのでもったいない。

しかし、特定の存在しないidが何度もアクセスされるのは問題の兆候だったりするから完全にmuteするのも気が引ける。皆さんどうしてるんでしょう?

追記:

ActiveRecord::RecordNotFoundエラーを防ぐ - komagata

teardownも同じくどっちでも変わらないと思ってた。しかし、前者は上書きだけど後者は親クラスのsetupも呼んでくれるので後者の方が良い。例えば下記みたいにintegrationテストだけは全部js実行できるブラウザでテストしたいけどいちいち書いてられない場合。

# test/test_helper.rb:
class ActionDispatch::IntegrationTest
  setup do
    Capybara.current_driver = Capybara.javascript_driver
  end
end
# test/integration/post_comment_test.rb:
class PostCommentTest < ActionDispatch::IntegrationTest
  setup do
    sign_in('foo@example.com', 'password')
  end

  test 'post a comment' do
    (...)
  end
end

superが要らないのは気持ちいい。

単純にテストの後処理としてのサインアウトだったらcookieを消す方が速い。

page.driver.browser.clear_cookies

サインアウト機能のテストでよくある。visitメソッドだとgetしか飛ばせない。

実際にサインアウトリンクをクリックするのはサインアウトのテストだったらいいけど、他のテストの後処理として使う場合はすごく遅くなりそうで嫌だ

deviseには下記のような設定ができるらしいけど、これテストしてることになんの?って気がするので却下。

config.sign_out_via = Rails.env.test? ? :get : :delete

これで行けた。

page.driver.submit :delete, '/users/sign_out', {}

「assertありゃええねん」

前回のdecoratorのテストのassertをminitest-power_assertに変えてみた。
# Gemfile:
group :test do
  gem 'minitest-power_assert'
end

わざと間違えてみる。

# test/decorators/user_decorator_test.rb:
require 'test_helper'

class UserDecoratorTest < ActiveSupport::TestCase
  def setup
    ActiveDecorator::ViewContext.current = controller.view_context
    @user = ActiveDecorator::Decorator.instance.decorate users(:jean)
  end

  test 'full_name' do
    assert { @user.full_name == 'Hugh Jackman' }
  end
end
$ rake test test/decorators/user_decorator_test.rb
Run options: --seed 52969

# Running:

F

Finished in 0.134274s, 7.4475 runs/s, 7.4475 assertions/s.

  1) Failure:
UserDecoratorTest#test_full_name [/Users/komagata/code/active_decorator-test_unit/test/decorators/user_decorator_test.rb:10]:

    assert { @user.full_name == 'Hugh Jackman' }
             |     |         |
             |     |         false
             |     "Jean Valjean"
             #<User id: 758109964, first_name: "Jean", last_name: "Valjean", website: "http://jeanvaljean.com", created_at: "2014-07-01 15:35:12", updated_at: "2014-07-01 15:35:12">

1 runs, 1 assertions, 1 failures, 0 errors, 0 skips

こりゃええ!

active_decoratorがtest_unitでもrspecと同じくそのままじゃhelperを使ったdecoratorのtestが書けないんだけど下記のようにすれば動く。(active_decoratorのREADMEに書いてあるdecoratorのテストの例)

# test/test_helper.rb:
class ActiveSupport::TestCase
  include ActionView::TestCase::Behavior
end
# test/decorators/user_decorater_test.rb:
require 'test_helper'

class UserDecoratorTest < ActiveSupport::TestCase
  def setup
    ActiveDecorator::ViewContext.current = controller.view_context
    @user = ActiveDecorator::Decorator.instance.decorate users(:jean)
  end 

  test 'full_name' do
    assert_equal 'Jean Valjean', @user.full_name
  end

  test 'link' do
    assert_equal '<a href="http://jeanvaljean.com">Jean Valjean</a>', @user.link
  end
end
$ rake test test/decorators/user_decorator_test.rb
Run options: --seed 37850

# Running:

..

Finished in 0.032828s, 60.9236 runs/s, 60.9236 assertions/s.

2 runs, 2 assertions, 0 failures, 0 errors, 0 skips

全部のコードはこちら

もっときれいな書き方があったらぜひ知りたいです。