ActiveSupport_TestCase + shoulda によるテスト の履歴(No.7)
更新- 履歴一覧
- 差分 を表示
- 現在との差分 を表示
- ソース を表示
- ソフトウェア/rails/ActiveSupport_TestCase + shoulda によるテスト へ行く。
- ActiveSupport::TestCase + shoulda による Ruby on Rails のテスト
- 環境
- いろいろ検討中
- モデル生成用パラメータ
- 独自拡張を読み込む
- assert_XXX
- shouda の matcher
- 役立ちそうな記事
- コメント
ActiveSupport::TestCase + shoulda による Ruby on Rails のテスト†
- ActiveSupport::TestCase は Rails 標準のテストフレームワーク
- shoulda は読みやすいテストケースを書くための拡張
これらを使って Rails アプリケーションをテストするためのノウハウをメモとして残したい
環境†
現在のこちらの環境は以下の通りです。
- rails 4.0.4
- minitest 4.7.5
- shoulda 3.5.0
いろいろ検討中†
まだちゃんとしたテストは書き始めたばかりなので全然やり方が定まってません。 このレベルの物をこんなところに晒すのも何なのですが、とりあえずメモとして書いちゃいます。
便利な gem†
- development 用
- better_errors+binding_of_caller
- エラー画面でコールスタック上の変数値を参照するなどいろいろできるようになる
- tapp
- 昔ながらの puts によるデバッグに於いて caller チェーンを切らずにデバッグ出力するのに便利
- test 用
- shoulda
- 読みやすいテストケースを書けるようにする
- database_rewinder
- データベースを高速に初期化する
- tapp
- 上記参照
Gemfile
group :development do gem 'better_errors' gem 'binding_of_caller' gem 'tapp' end group :test do gem 'database_rewinder' gem 'shoulda' gem 'tapp' end
shoulda の基本†
shoulda では、
- context の setup で操作を行い
- should で検証を行う
- テストの内容説明は context + should で自動生成される
というのが基本になります。
生の ActiveSupport::TestCase とは考え方が異なるので始めは戸惑います。
ActiveSupport::TestCase であれば、
- setup ですべての test に共通の前処理を行い
- 個々の test で操作を行い、さらに assert も行う
- テストの内容説明は test の名前 + assert の記述で行う
となります。
shoulda の場合は「個々の should の中で操作を行わない」 という点が大きな違いなわけです。
無理矢理対応関係を書けば次のようになります。
| shoulda | assert | |
|---|---|---|
| 操作 | context #1 | setup |
| context #2 | ||
| context #3 | test | |
| 検証 | should | assert |
下で言えば、
LANG:ruby
[:ja,:en].each do |locale|
context "with locale = #{locale}," do
setup {
# ロケール設定
}
context "with auto-login," do
setup {
# オートログイン準備
}
context 'GET #home' do
setup {
get :home # 実際の操作
}
should 期待される結果1
should 期待される結果2
end
end
end
などとなっています。
これで、
- with locale ja, with auto-login, GET #home should 期待される結果1
- with locale ja, with auto-login, GET #home should 期待される結果2
- with locale en, with auto-login, GET #home should 期待される結果1
- with locale en, with auto-login, GET #home should 期待される結果2
のようにすべてのテストに適切な説明が付きます。
エラーメッセージの例:
LANG:console ... 1) Failure: AccountControllerTest#test_: with locale en, without login, valid POST #create should send email to registered address. [/home/osamu/MyApp/test/controllers/account_controller_test.rb:141]: Expected true to be nil or false ...
should の中に get :home を書いてしまうと、 should にうまく名前を付けられなくて困ることになります。
- context の setup は、中に含まれるすべての should に対して毎回呼ばれます。
- すべての should に先駆けて一回だけ行いたいような初期化処理がもしあれば、 テストケースの宣言部分(クラススコープ)で行えば良いのだと思います。 ただ、個々のテストは独立していないといろいろややこしいので、 そういう初期化が必要になることはあまり無いんじゃないかとも思います。
コントローラのテスト†
途中ロケールの処理が入っていますが、そのあたりの構成は ソフトウェア/rails/国際化 に書いた通りです。
test/controllers/account_controller_test.rb
LANG:ruby(linenumber)
#coding: utf-8
require 'test_helper'
require_relative '../models/_test_params.rb'
class AccountControllerTest < ActionController::TestCase
tests AccountController # この例のように標準的な命名をしている限り、
# コントローラ名の指定は省略してもよい
# Params::param_name の形で Hash 形式のモデル作成用
# パラメータを参照可能にする
# Params::param_name(key1: value1, key2: value2) として
# いくつかのキーをオーバーライドすることも可能
include TestParams
# setup とテストケース should の中とで情報をやりとりするのに
# 普通にインスタンス変数 @xxx を使うか、反則技でクラス変数 @@xxx を
# 使うか迷い中
#
# インスタンス変数を使うのが普通だが、
# - クラススコープからインスタンス変数にアクセスできなくて
# 残念に感じることがたまにある
# - 比較的多数のインスタンス変数がフレームワークによって
# 使われているため、命名の衝突が怖い
#
# クラス変数を使うのはかなり異端
# - 本来の目的からずれまくってるので完全に反則技
# - 初期化し忘れると直前のテストケースの値が残ってしまうのも問題
# - その代わり、どこからでもアクセスできるのがかなり便利?
# ActionController::TestCase にて、以下のクラス変数が使われている。
#
# @@required_fixture_classes: false
# @@already_loaded_fixtures: {}
# @@test_suites: {ActiveSupport::TestCase=>true, ...}
# @@current: #<AccountControllerTest:0xb192174>
#
# クラス変数を使う場合は、これらおよび未来の TestCase のクラス変数と
# 名前が被らないよう注意する必要あり
# インスタンス変数として以下が定義されるので、名前が
# 被らないように注意が必要
#
# インスタンス変数 利用時の記法 内容
# @__name__ @__name__ 現在実行中のテストのタイトルが入る
# @__io__
# @passed
# @_assertions
# @fixture_cache
# @fixture_connections
# @loaded_fixtures
# @tagged_logger
# @_partials
# @_templates
# @_layouts
# @_files
# @request request #<ActionController::TestRequest>
# @response response #<ActionController::TestResponse>
# @controller @controller #<Controller under testing>
# @routes @routes #<ActionDispatch::Routing::RouteSet>
# @shoulda_context
# @html_document html_document #<HTML::Document>
# @assigns assigns @controller 内で定義されたインスタンス変数
# すべての should に先駆けて1回だけ行いたい処理がもしもあれば
# ここに書けばいい(通常あまり必要なさそう?)
# 初期化処理1
# 初期化処理2
# すべての context に共通の初期化処理
# これは個々の should の直前に、毎回呼ばれることになる
setup {
clear_all
}
def clear_all
ActionMailer::Base.deliveries.clear
DatabaseRewinder.clean_all
session.clear
cookies.clear
end
# 以下すべてを ja, en 両方のロケールで試す
[:ja,:en].each do |locale|
context "with locale #{locale}," do
setup {
request.env['HTTP_ACCEPT_LANGUAGE'] = locale.to_s
}
# ここからが本当のテスト内容
context "without login," do
context 'GET #home' do
setup {
get :home
}
should redirect_to_path :account_login
should set_the_flash # login first
should 'set session_intended_path to requested path' do
# セッションキーは ApplicationController で
# @session_intended_path="hogehoge"
# として与えられているので、assigns を使って読み取る
assert_equal @request.path,
session[assigns(:session_intended_path)]
end
end
context "GET #new" do
setup {
get :new
}
should respond_with :success
should render_template :new
should_not set_the_flash
end
context 'invalid POST #create' do
setup {
post :create, account: Params::invalid_account
}
should "not create account" do
assert_equal 0, Account.count
end
should render_template :new
end
context 'valid POST #create' do
setup {
post :create, account: Params::valid_account
}
should "create account" do
assert_equal 1, Account.count
end
should set_the_flash # account created
should redirect_to_path :account_login
should "send email to registered address" do
assert_not ActionMailer::Base.deliveries.empty?
email_sent = ActionMailer::Base.deliveries.last
assert_equal Params::valid_account[:email], email_sent.to
end
end
end
context "with auto-login," do
setup {
@@account = Account.create!(
Params::valid_account(
password: Account.generate_new_password,
auto_login: Account.generate_new_password
)
)
cookies[assigns(:cookie_auto_login)] =
[@@account.auto_login, @@account.email].join(':')
}
context 'GET #home' do
setup {
get :home
}
should respond_with :success
should 'update autologin password at successful auto login' do
assert_not_equal @@account.auto_login, assigns[:account].auto_login
assert_equal assigns[:account].auto_login,
cookies[assigns[:cookie_auto_login]].split(':',2)[0]
end
end
context 'GET #new' do
setup {
get :new
}
should set_the_flash # already login
should redirect_to_path :account
end
context 'GET #edit' do
setup {
get :edit
}
should respond_with :success
should render_template :edit
end
context 'POST #update' do
setup {
post :update, account: {name: "Updated Name"}
}
should 'update name' do
account = Account.find_by_email(@@account.email)
assert_equal "Updated Name", account.name
end
should set_the_flash # successfully updated
should redirect_to_path :account
end
context 'DELETE #destroy' do
setup {
delete :destroy
}
should "delete account" do
assert_equal 0, Account.count
end
should set_the_flash # successfully deleted
should redirect_to_path :default
end
context 'with valid registration,' do
setup {
@@account.registration =
Registration.create! Params::valid_registration
}
context 'DELETE #destroy' do
setup {
delete :destroy
}
should "not delete account" do
assert_equal 1, Account.count
end
should set_the_flash # can not delete
should redirect_to_path :account
end
end
end
end
end
end
モデル生成用パラメータ†
test/models/_test_params.rb
LANG:ruby(linenumber)
# coding: utf-8
module TestParams
module Params
#
# name で指定される名前のクラス関数を作成する
# Params::param_name = ハッシュを返す
# Params::param_name(key: value) = ハッシュに hash[:key]=value してから返す
#
def self.param(param_name, hash)
class_eval <<-EOS
def self.#{param_name}(options={})
#{hash.inspect}.merge(options)
end
EOS
end
param :valid_account, {
name: "Foo Baa",
email: "account@example.com",
affiliation: "My institute",
address: "My address",
telephone: "+00-000-0000"
}
param :invalid_account, {
name: ''
}
param :valid_registration, {
gender: 1,
membership: 1,
student: false,
receipt_address: 'My address',
payaccount: 'My Account'
}
param :invalid_registration, {
gender: 1,
membership: 1,
student: false,
receipt_address: nil,
payaccount: nil
}
end
end
独自拡張を読み込む†
test/test_helper.rb
LANG:ruby
...
# I18n.translate の翻訳の抜けを検出する
require 'test_i18n_translation_strictly'
# shoulda 用のマクロを読み込む
Dir[File.expand_path('../shoulda_macros', __FILE__) << '/*.rb'].each do |file|
require file
end
...
I18n.translate の翻訳の抜けを検出する†
config/initializers/test_i18n_translation_strictly.rb
LANG:ruby(linenumber)
# coding: utf-8
# 翻訳が見付からないときに例外を投げる
# I18n.exception_handler
# http://guides.rubyonrails.org/i18n.html
# これだけだとモデル名やカラム名の翻訳が無くても
# エラーにならないので、
# ActiveRecord::model_name.human
# https://github.com/rails/rails/blob/master/activemodel/lib/active_model/translation.rb#L43
# ActiveRecord::human_attribute_name
# https://github.com/rails/rails/blob/master/activemodel/lib/active_model/naming.rb#L175
# にも手を入れる。locale == :en では、モデル名やカラム名を
# そのまま使えることも多いので、それ以外の時に翻訳が未設定
# であれば例外を投げる
#
# 後者2つについては関数のコードをほぼ丸々コピーしているので、
# 元のコードが変更されたらそれに合わせて変更しなければならない
module I18n
class JustRaiseExceptionHandler < ExceptionHandler
def call(exception, locale, key, options)
if exception.is_a?(MissingTranslation) and
key.to_s != 'i18n.plural.rule'
raise exception.to_exception
else
super
end
end
end
end
I18n.exception_handler = I18n::JustRaiseExceptionHandler.new
module ActiveModel
class Name
def human(options={})
return @human unless @klass.respond_to?(:lookup_ancestors) &&
@klass.respond_to?(:i18n_scope)
defaults = @klass.lookup_ancestors.map do |klass|
klass.model_name.i18n_key
end
defaults << options[:default] if options[:default]
# defaults << @human
defaults << @human if I18n.locale == :en
options = { scope: [@klass.i18n_scope, :models], count: 1, default: defaults }.merge!(options.except(:default))
I18n.translate(defaults.shift, options)
end
end
end
module ActiveModel
module Translation
def human_attribute_name(attribute, options = {})
options = { count: 1 }.merge!(options)
parts = attribute.to_s.split(".")
attribute = parts.pop
namespace = parts.join("/") unless parts.empty?
attributes_scope = "#{self.i18n_scope}.attributes"
if namespace
defaults = lookup_ancestors.map do |klass|
:"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
end
defaults << :"#{attributes_scope}.#{namespace}.#{attribute}"
else
defaults = lookup_ancestors.map do |klass|
:"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}"
end
end
defaults << :"attributes.#{attribute}"
defaults << options.delete(:default) if options[:default]
# defaults << attribute.humanize
defaults << attribute.humanize if I18n.locale == :en
options[:default] = defaults
I18n.translate(defaults.shift, options)
end
end
end
should redirect_to_path†
test/shoulda_macros/action_controller_macros.rb
LANG:ruby(linenumber)
class ActionController::TestCase
# shoulda の redirect_to では、
# should redirect_to account_path
# should redirect_to account_path(action: :edit)
# should redirect_to url_for(controller: :account, action: :edit)
# などとは書けないので、かわりに
# should redirect_to("account_path"){ account_path }
# should redirect_to("account_path(action: :edit)"){ account_path(action: :edit) }
# should redirect_to("controller: :account, action: :edit"){
# @controller.url_for(controller: :account, action: :edit) }
# などと書かなければならない。より簡単に
# should redirect_to_path :account
# should redirect_to_path :account, action: :edit
# should redirect_to_path controller: :account, action: :edit
# などと書けるようにするのがこの関数
def self.redirect_to_path(*arg)
if arg.first.is_a? Symbol
redirect_to_path_arg = arg.dup
redirect_to_path_symbol = :"#{redirect_to_path_arg.shift}_path"
if redirect_to_path_arg.empty?
redirect_to(redirect_to_path_symbol){
__send__(redirect_to_path_symbol)
}
else
redirect_to_path_arg_s =redirect_to_path_arg.inspect.gsub(/\A\[|\]\z/,'')
redirect_to_path_arg_s.gsub!(/\A\{|\}\z/,'') if redirect_to_path_arg.count == 1
redirect_to("#{redirect_to_path_symbol}(#{redirect_to_path_arg_s})", &Proc.new {
__send__(redirect_to_path_symbol, *redirect_to_path_arg)
})
end
else
redirect_to_path_arg = arg.dup
redirect_to_path_arg_s =redirect_to_path_arg.inspect.gsub(/\A\[|\]\z/,'')
redirect_to_path_arg_s.gsub!(/\A\{|\}\z/,'') if redirect_to_path_arg.count == 1
redirect_to(redirect_to_path_arg_s){
@controller.url_for *redirect_to_path_arg
}
end
end
end
context_for_all_locales†
というのも作っておくと良さそう
エラーの生じた should を特定しやすくする†
shoulda からのエラーメッセージが以下のように shoulda ライブラリや shoulda macro の中を指してしまうことがあります。
LANG:console 1) Failure: AccountControllerTest#test_: with locale en, without login, invalid POST #create should set the flash. [/var/lib/gems/2.0.0/gems/shoulda-context-1.2.1/lib/shoulda/context/context.rb:344]: Expected the flash to be set, but no flash was set
このようなメッセージからエラーが生じたテストの場所を特定するのはなかなか手間がかかります。
ごたごたは飛ばして解決策へ飛ぶ場合は こちら
試行錯誤†
で、例外のバックトレースをいじって改善できない物か???と考えたわけです。
ActiveSupport::TestCase
https://github.com/rails/rails/blob/master/activesupport/lib/active_support/test_case.rb
gem 'minitest' を取り込んで ::Minitest::Test を継承している。ruby 標準の minitest ではないらしい?
これかな?
https://github.com/seattlerb/minitest
capture_exceptions で self.failures にエラーを溜めて、
https://github.com/seattlerb/minitest/blob/master/lib/minitest/test.rb#L202
to_s で文字に直す?
https://github.com/seattlerb/minitest/blob/master/lib/minitest/test.rb#L261
場所を表わす部分は location で、self.failure というのがどこから来るのか分からないものの、
アサート例外の location から得ているように見える
https://github.com/seattlerb/minitest/blob/master/lib/minitest/test.rb#L224
failure は Runnable で定義されていた → 何のことはない failures.first のことだった
https://github.com/seattlerb/minitest/blob/master/lib/minitest.rb#L345
Minitest::Assertion 例外の location はここ
https://github.com/seattlerb/minitest/blob/master/lib/minitest.rb#L633
LANG:ruby
module Minitest
VERSION = "5.3.4" # :nodoc:
...
class Assertion < Exception
...
def location
last_before_assertion = ""
self.backtrace.reverse_each do |s|
break if s =~ /in .(assert|refute|flunk|pass|fail|raise|must|wont)/
last_before_assertion = s
end
last_before_assertion.sub(/:in .*$/, "")
end
この backtrace を遡っている部分の条件を変更してやれば良い・・・のかと思ったら違うみたいです??? どこかで間違った???
ああ、動かしてるバージョンが段違いでした orz
上記 seattlerb/minitest でメンテしているのは 5.x.x 系列で、4.x.x 系列は 4.7.2 あたりが最後?
古いコードだと、普通に rescue で捕まえて runner.puke してますね
https://github.com/seattlerb/minitest/blob/7e25922c340fa1cfdbeffaf8f37ec020cf6a4fff/lib/minitest/unit.rb#L1255
puke は location を呼んでるから、
https://github.com/seattlerb/minitest/blob/7e25922c340fa1cfdbeffaf8f37ec020cf6a4fff/lib/minitest/unit.rb#L954
この location を書き換えればいいのかと思ったのですが・・・
LANG:ruby
module Minitest
class Unit
def location e
e.backtract.join("\n")
end
end
end
として得られたトレースはこんな感じ。
LANG:console minitest-4.7.5/lib/minitest/unit.rb:195:in `assert' shoulda-context-1.2.1/lib/shoulda/context/assertions.rb:91:in `safe_assert_block' shoulda-context-1.2.1/lib/shoulda/context/assertions.rb:83:in `assert_rejects' shoulda-context-1.2.1/lib/shoulda/context/context.rb:358:in `block in should_not' shoulda-context-1.2.1/lib/shoulda/context/context.rb:413:in `instance_exec' shoulda-context-1.2.1/lib/shoulda/context/context.rb:413:in `block in create_test_from_should_hash' minitest-4.7.5/lib/minitest/unit.rb:1258:in `run' minitest-4.7.5/lib/minitest/unit.rb:933:in `block in _run_suite' minitest-4.7.5/lib/minitest/unit.rb:926:in `map' minitest-4.7.5/lib/minitest/unit.rb:926:in `_run_suite' minitest-4.7.5/lib/minitest/parallel_each.rb:71:in `block in _run_suites' minitest-4.7.5/lib/minitest/parallel_each.rb:71:in `map' minitest-4.7.5/lib/minitest/parallel_each.rb:71:in `_run_suites' minitest-4.7.5/lib/minitest/unit.rb:877:in `_run_anything' minitest-4.7.5/lib/minitest/unit.rb:1085:in `run_tests' minitest-4.7.5/lib/minitest/unit.rb:1072:in `block in _run' minitest-4.7.5/lib/minitest/unit.rb:1071:in `each' minitest-4.7.5/lib/minitest/unit.rb:1071:in `_run' minitest-4.7.5/lib/minitest/unit.rb:1059:in `run' minitest-4.7.5/lib/minitest/unit.rb:795:in `block in autorun'
バックトレースに TestCase の行が含まれていない???
https://github.com/thoughtbot/shoulda-context/blob/master/lib/shoulda/context/context.rb#L356
LANG:ruby
def should_not(matcher)
name = matcher.description
blk = lambda { assert_rejects matcher, subject }
self.shoulds << { :name => "not #{name}", :block => blk }
end
この lambda で作った blk が shoulds に溜められて、後から順に実行され、そこでエラーが出ると。
これだと例外から行番号を得るのは難しいですね。
解決†
むしろ、この :name に行番号を突っ込んじゃえばいいという話かも。
test/shoulda_macros/add_caller_information.rb
LANG:ruby
#coding: utf-8
module Shoulda
module Context
class Context # :nodoc:
# should ブロックの :name に行番号を書き足す
def add_caller_line_number(should)
# 現バージョンでは2番目が呼び出し元になるみたい
caller(2).first =~ /:(\d+):in /
should[:name] = should[:name].to_s + " (at line #{$1})"
end
alias :should_without_caller_information :should
def should(name_or_matcher, options = {}, &blk)
count_before = self.shoulds.count
should_without_caller_information(name_or_matcher, options = {}, &blk)
if self.shoulds.count > count_before
add_caller_line_number(self.shoulds.last)
else
add_caller_line_number(self.should_eventuallys.last)
end
end
alias :should_not_without_caller_information :should_not
def should_not(matcher)
should_not_without_caller_information(matcher)
add_caller_line_number(self.shoulds.last)
end
end
end
end
これで、
LANG:console 3) Failure: AccountControllerTest#test_: with locale ja, without login, GET #home should not set the flash (at line 35). [/var/lib/gems/2.0.0/gems/shoulda-context-1.2.1/lib/shoulda/context/context.rb:358]: Did not expect the flash to be set, but was #<ActionDispatch::Flash::FlashHash:0xad041fc ...
などというように、should や should_not に対応する行番号が (at line xxx) の形で付くようになりました。
caller() は遅いので、called_from を作った、という記事がありました。テストの規模が大きい場合には検討の価値があるかもしれません。(このライブラリ、Ruby 1.8 以外に対応していない?)
http://magazine.rubyist.net/?0031-BackTrace#l7
とはいえ1万個の should を作ってようやく1秒違うかどうかなので、気にするだけ損なのかも。
assert_XXX†
| mt AS AS dep AD AC sd jr で実装される assert_xxxx | ||||
|---|---|---|---|---|
| 名前 | 引数 | テスト内容 | refuse | 実装 |
| assert | test, msg = nil | test | ○ | mt |
| assert_accepts | matcher, target, options = {} | matcher.matches?(target) | sd | |
| assert_blank | obj, msg=nil | obj.blank? | AS | |
| assert_contains | collection, x, extra_msg = "" | x can be regexp | sd | |
| assert_deprecated | match = nil, &block | AS | ||
| assert_difference | expression, diff = 1, msg = nil, &block | block 前後で変化 | AS | |
| assert_does_not_contain | collection, x, extra_msg = "" | x can be regexp | sd | |
| assert_dom_equal | expected, actual, message = "" | HTML::Document.new(?).rootを比較 | AD | |
| assert_dom_not_equal | expected, actual, message = "" | HTML::Document.new(?).rootを比較 | AD | |
| assert_empty | obj, msg = nil | obj.empty? | ○ | mt |
| assert_equal | exp, act, msg = nil | exp == act | ○ | mt |
| assert_generates | expected_path, options, defaults={}, extras = {}, message=nil | AD | ||
| assert_in_delta | exp, act, delta = 0.001, msg = nil | (act-exp).abs <= delta | ○ | mt |
| assert_in_epsilon | a, b, epsilon = 0.001, msg = nil | 上の別名 | ○ | mt |
| assert_includes | collection, obj, msg = nil | collection.include?(obj) | ○ | mt |
| assert_instance_of | cls, obj, msg = nil | obj.instance_of?(cls) | ○ | mt |
| assert_kind_of | cls, obj, msg = nil | obj.kind_of?(cls) | ○ | mt |
| assert_match | matcher, obj, msg = nil | matcher =~ obj | ○ | mt |
| assert_nil | obj, msg = nil | obj.nil? | ○ | mt |
| assert_no_difference | expression, msg = nil, &block | block 前後で保存 | AS | |
| assert_no_match | matcher, obj, msg = nil | matcher !~ obj | mt | |
| assert_no_tag | *opts | html_document.find(*opts) | AD | |
| assert_not | obj, msg = nil | !obj | AS | |
| assert_not_deprecated | &block | AS | ||
| assert_not_empty | obj, msg = nil | !obj.empty? | mt | |
| assert_not_equal | exp, act, msg = nil | exp != act | mt | |
| assert_not_in_delta | exp, act, delta = 0.001, msg = nil | (act-exp).abs > delta | mt | |
| assert_not_in_epsilon | a, b, epsilon = 0.001, msg = nil | 上の別名 | mt | |
| assert_not_includes | collection, obj, msg = nil | !collection.include?(obj) | mt | |
| assert_not_instance_of | cls, obj, msg = nil | !obj.instance_of?(cls) | mt | |
| assert_not_kind_of | cls, obj, msg = nil | !obj.kind_of?(cls) | mt | |
| assert_not_nil | obj, msg = nil | !obj.nil? | mt | |
| assert_not_operator | o1, op, o2 = UNDEFINED, msg = nil | !o1._send_(op, o2) | mt | |
| assert_not_predicate | o1, op, msg = nil | !o1._send_(op) | mt | |
| assert_not_respond_to | obj, meth, msg = nil | !obj.respond_to?(meth) | mt | |
| assert_not_same | exp, act, msg = nil | !exp.equal?(act) | mt | |
| assert_nothing_raised | ||||
| assert_operator | o1, op, o2 = UNDEFINED, msg = nil | o1._send_(op, o2) | ○ | mt |
| assert_output | stdout = nil, stderr = nil | mt | ||
| assert_predicate | o1, op, msg = nil | o1._send_(op) | ○ | mt |
| assert_raise | ||||
| assert_raises | *exp | mt | ||
| assert_recognizes | expected_options, path, extras={}, msg=nil | AD | ||
| assert_redirected_to | options = {}, message=nil | AD | ||
| assert_rejects | matcher, target, options = {} | !matcher.matches?(target) | sd | |
| assert_respond_to | obj, meth, msg = nil | obj.respond_to?(meth) | ○ | mt |
| assert_response | type, message = nil | AD | ||
| assert_routing | path, options, defaults={}, extras={}, message=nil | AD | ||
| assert_same | exp, act, msg = nil | exp.equal?(act) | ○ | mt |
| assert_same_elements | a1, a2, msg = nil | compare array asif it is set | sd | |
| assert_select | *args, &block | AD | ||
| assert_select_email | &block | AD | ||
| assert_select_encoded | element = nil, &block | AD | ||
| assert_select_jquery | *args, &block | jr | ||
| assert_send | [recv, msg, *args], m = nil | recv._send_(msg, *args) | mt | |
| assert_silent | assert_output "", "" | mt | ||
| assert_tag | *opts | html_document.find(*opts) | AD | |
| assert_template | options = {}, message = nil | AC | ||
| assert_throws | sym, msg = nil | mt | ||
- mt: minitest = https://github.com/seattlerb/minitest/blob/7e25922c340fa1cfdbeffaf8f37ec020cf6a4fff/lib/minitest/unit.rb#L190
- AS: ActiveSupport = https://github.com/rails/rails/blob/4-0-stable/activesupport/lib/active_support/testing/assertions.rb- AS: ActiveSupport deplication = https://github.com/rails/rails/blob/4-1-stable/activesupport/lib/active_support/testing/deprecation.rb
- AD: ActionDispatch = https://github.com/rails/rails/tree/4-0-stable/actionpack/lib/action_dispatch/testing/assertions
- AC: ActionController = https://github.com/rails/rails/blob/4-0-stable/actionpack/lib/action_controller/test_case.rb#L95
- sd: Shoulda = https://github.com/thoughtbot/shoulda-context/blob/master/lib/shoulda/context/assertions.rb
- jr: jquery-rails = https://github.com/rails/jquery-rails/blob/master/lib/jquery/assert_select.rb
AD: with_routing
AD: css_select
shouda の matcher†
ActiveModel Matchers†
| ActiveRecord Matchers†
| ActionController Matchers†
|
始めからコード側で宣言的に書かれている、例えば has_many なんかを わざわざテストしても、ほとんどコピペになるだけで意味は薄いと思います。
なので、上記のように matcher はたくさんある物の、頻繁に使うのは、
ActionModel の
- allow_value
- allow_value('http://foo.com', 'http://bar.com/baz').for(:website_url)
- allow_value('2013-01-01').for(:birthday_as_string).on(:create)
- allow_value('open', 'closed').for(:state).with_message('State must be open or closed')
ActionController の
- redirect_to
- redirect_to :list
- redirect_to { posts_path }
- render_template
- render_template 'show'
- render_template :show
- respond_with
- respond_with 403
- respond_with :success
- respond_with 500..600
- route
- route(:get, '/posts').to(controller: 'posts', action: 'index')
- route(:get, '/posts/1').to('posts#show', id: 1)
- set_session
- set_session :key
- set_session(:key).to("value")
- set_session(:key).to{ calc_expectation }
- set_the_flash
- set_the_flash
- set_the_flash.to('value')
- set_the_flash.to(/regexp/)
- set_the_flash[:key]
- set_the_flash[:key].to('value')
- set_the_flash[:key].to(/regexp/)
- set_the_flash[:key].now
- set_the_flash[:key].to('value').now
くらいかもしれません。
matcher の作り方†
set_session のコードを見れば一通り分かりそうですね。