概要
minitest-focus と shoulda-context との組み合わせがうまく動かないので、shoulda-context と共に使える focus を実装してみました。
背景
minitest-focus
Minitest::Unit によるテストを便利にするための gem として minitest-focus というものがあります。
これは、 編集中のテストケースの中で、ある特定のテストだけを実行したいときに そのテストに focus
というキーワードを付けることで、同じテストケースの中の他のテストをすべてスキップできる、という便利な gem です。
特に guard-minitest でテストを自動実行しているときには非常に便利です。
https://github.com/guard/guard-minitest
こんな風にして使います。
require "minitest/autorun"
require "minitest/focus"
class MyTest < MiniTest::Unit::TestCase
def test_unfocused
# this test will be skipped
end
focus # <<< FOCUSED!
def test_focused_test
# only this one will run
end
def test_unfocused_too
# this test will be skipped, too
end
end
shoulda-context
一方、Minitest::Unit で BDD 的なテストケースを書ける shoulda-context という gem があって、rspec ではなく Minitest ベースでテストを書いている場合には、かなりよく使われているのだと思います。
問題点
問題は、minitest-focus は shoulda-context との組み合わせがうまく動かないことです。
shoulda-context は、その内部でテスト関数をたくさん作成するのですが、普通に書いている限りそこに focus を付けるすべがありません。
解決策
そこで、shoulda-context と合わせて使うための focus を以下のように作ってみました。これは minitest-focus とは独立して、単独で動作します(というか、たぶん両方入れると誤動作します)。
使用例
次のように、context
または should
, should_not
に focus
を付けることができます。
require 'shoulda-focus'
class MyTest < MiniTest::Unit::TestCase
should "will not be tested" do assert false end
context "Unfocused context" do
should "will not be tested" do assert false end
focus # <<<<<< FOCUSED!
should "will be tested" do assert true end
should "will not be tested" do assert false end
end
focus # <<<<<< FOCUSED!
context "Focused context" do
should "will be tested" do assert true end
should "will be tested" do assert true end
should "will be tested" do assert true end
context "Focused sub context" do
should "will be tested" do assert true end
end
end
end
ソース
# shoulda-focus.rb
#
# tested with: shoulda (3.5.0), minitest (4.7.5)
#
# Marking a context or a should by 'focus'
# allows testing only the focused tests,
# skipping all other unfocused ones in a TestCase.
#
# REVISIONS:
# 2014-05-30 report number of skipped tests in the result
#
# EXAMPLE:
#
# class MyTest < ActionController::TestCase
#
# should "will not be tested" do assert false end
#
# context "Unfocused context" do
# should "will not be tested" do assert false end
#
# focus # <<<<<< FOCUSED!
# should "will be tested" do assert true end
#
# should "will not be tested" do assert false end
# end
#
# focus # <<<<<< FOCUSED!
# context "Focused context" do
# should "will be tested" do assert true end
# should "will be tested" do assert true end
# should "will be tested" do assert true end
# context "Focused sub context" do
# should "will be tested" do assert true end
# end
# end
#
# end
#
module Shoulda
module Context
class << self
attr_accessor :focused # indicates we are in a focused context
attr_writer :focus_next # indicates focus just called, we will focus the next context/should
attr_accessor :focus_used # indicates focus is used, then we will skip unfocused shoulds
# to see if we should focus the next context
def to_focus?
# either we are in a focused context or 'focus' was just called
result = @focused || @focus_next
@focus_next = false
result
end
end
module ClassMethods
# call this method to mark the next context/should to be focused
def focus
unless Shoulda::Context.focus_used
puts "",
" * FOCUS CALLED FOR [#{self.name}] WE WILL SKIP UNFOCUSED TESTS",
" " + caller(2).first.sub(/:in .*/, ''),
""
end
Shoulda::Context.focus_used = true
Shoulda::Context.focus_next = true
end
end
class Context
alias :initialize_without_focus :initialize
def initialize(name, parent, &blk)
was_focused = Shoulda::Context.focused
Shoulda::Context.focused = Shoulda::Context.to_focus?
initialize_without_focus(name, parent, &blk)
Shoulda::Context.focused = was_focused
end
alias :should_without_focus :should
def should(name_or_matcher, options = {}, &blk)
to_focus = Shoulda::Context.to_focus?
count_before = shoulds.count
should_without_focus(name_or_matcher, options, &blk)
if shoulds.count > count_before
shoulds.last[:focused] = to_focus
else
should_eventuallys.last[:focused] = to_focus
end
end
alias :should_not_without_focus :should_not
def should_not(matcher)
to_focus = Shoulda::Context.to_focus?
should_not_without_focus(matcher)
shoulds.last[:focused] = to_focus
end
alias :create_test_from_should_hash_without_focus :create_test_from_should_hash
def create_test_from_should_hash(should)
if not Shoulda::Context.focus_used or should[:focused]
create_test_from_should_hash_without_focus(should)
else
else
# to report skipped tests verbosely, use skip() instead (will be too noisy and slow)
# should[:block] = Proc.new(){ skip() }
should[:block] = Proc.new(){ skip_quietly() }
create_test_from_should_hash_without_focus(should)
end
end
end
end
end
MiniTest::Unit::TestCase.class_eval do
alias :run_without_quiet_skip :run
def run(runner)
@runner = runner
run_without_quiet_skip(runner)
end
def skip_quietly()
@runner.instance_eval do
@skips += 1
end
end
end
勉強中
その他、shoulda を使った Rails のテストについて調べた内容を以下のページにまとめています