はじめに
RSpec の Formatter をデフォルトのままにしている人は以下の様に表示されると思います。
.
は成功したテスト
*
は pending したテスト
F
は失敗したテスト
で、これ以降に失敗したテストの詳細が出るわけですが、、、
いくらなんでも寂しすぎる!!
という事でもっと素敵な Progress Bar を表示させるべく RSpec の Formatter を書き換えます。
変更は以下のステップでやっていきます。
- 色を付ける
- Fuubar を入れる
- 自作する
ではやってみます。
色を付ける
これはスーパー簡単で、--color
オプションをつけて実行するだけです。
これだけでもだいぶ見やすい。
毎回使うオプションは .rspec
に定義してあげるといいです。
--color
Fuubar を入れる
簡素だった Progress Bar をいい感じにしてくれる gem です。
実際に fuubar を入れて実行してみると
gem 'fuubar'
% bin/rspec spec/ --format Fuubar
現在の進捗をリアルタイム表示してくれて
かかっている時間も表示されています。
Pending しているテストがあると黄色になって、
失敗すると赤くなります。
手元でやってもらうとわかりますが、失敗の詳細が上部に表示されるなど、他にもわかりやすくなっています。
素晴らしい!
カスタマイズもできて、例えば以下の様に RSpec の config に書くと
config.fuubar_progress_bar_options = {format: 'Hogehoge: [%b%i] %a'}
こうなります。
いいですね。Fuubar。
自作する
もう Fuubar で十分すぎる気がしますが、ここはぐっとこらえて自作していきます。
実行結果や、失敗詳細などいろいろとカスタマイズすることができますが、今回は Fuubar と同様に実行時に出力される Progress Bar をカスタマイズします。
Progress Bar のインタフェースは以下になります。
class ProgressFormatter < BaseTextFormatter
Formatters.register self, :example_passed, :example_pending, :example_failed, :start_dump
def example_passed(_notification)
output.print ConsoleCodes.wrap('.', :success)
end
def example_pending(_notification)
output.print ConsoleCodes.wrap('*', :pending)
end
def example_failed(_notification)
output.print ConsoleCodes.wrap('F', :failure)
end
def start_dump(_notification)
output.puts
end
end
-
example_passed
はテストが成功した際 -
example_pending
は pending されたテストが呼ばれた際 -
example_passed
はテストが失敗した際 -
start_dump
は全テストが完了して、テスト結果のメッセージが表示される前
に呼ばれます。
こいつらを override していけば Progress Bar を自作することができます。
例えば、以下のようなファイルを用意します。
require 'rspec/core'
require 'rspec/core/formatters/base_text_formatter'
class CustomBar < RSpec::Core::Formatters::BaseTextFormatter
RSpec::Core::Formatters.register self, :example_passed, :example_pending, :example_failed, :start_dump
def example_passed(_notification)
output.print RSpec::Core::Formatters::ConsoleCodes.wrap('Success!', :success)
end
def example_pending(_notification)
output.print RSpec::Core::Formatters::ConsoleCodes.wrap('Pending!', :pending)
end
def example_failed(_notification)
output.print RSpec::Core::Formatters::ConsoleCodes.wrap('Failure!', :failure)
end
def start_dump(_notification)
output.puts
output.puts 'テスト終了!!'
end
end
自作した Formatter は、実行時に --require
オプションで指定して読み込んで、--format
オプションで指定してあげます。
% bin/rspec spec/ --require /Users/Shohei/Desktop/custom_bar.rb --format CustomBar
できた!見づらい!
でもまあ、あとはお絵かきを頑張るだけです。
....
頑張った汚いコードがこちらです。
require 'rspec/core'
require 'rspec/core/formatters/base_text_formatter'
class CustomBar < RSpec::Core::Formatters::BaseTextFormatter
RSpec::Core::Formatters.register self, :example_passed, :example_pending, :example_failed, :start_dump
attr_accessor :progress_count, :success_count, :failed_count, :max_length
def initialize(output)
super output
self.progress_count = 0
self.success_count = 0
self.failed_count = 0
self.max_length = IO.console.winsize.last
end
def increment
self.progress_count += 1
end
def increment_success_count
self.success_count += 1
end
def increment_failed_count
self.failed_count += 1
end
def example_passed(_notification)
increment
increment_success_count
output.print RSpec::Core::Formatters::ConsoleCodes.wrap(hashiru, :success)
back_pointer(hashiru)
end
def example_pending(_notification)
increment
output.print RSpec::Core::Formatters::ConsoleCodes.wrap(tomaru, :pending)
back_pointer(tomaru)
end
def example_failed(_notification)
increment
increment_failed_count
output.print RSpec::Core::Formatters::ConsoleCodes.wrap(korobu, :failure)
back_pointer(korobu)
end
def start_dump(_notification)
if self.failed_count.zero?
output.print RSpec::Core::Formatters::ConsoleCodes.wrap(goal, :success)
else
output.print RSpec::Core::Formatters::ConsoleCodes.wrap(failed, :failure)
end
end
def now_position
(self.max_length / @example_count.to_f * self.success_count).to_i
end
def back_pointer(str)
strs = str.split("\n")
line_num = strs.count
output.print "\e[#{line_num}A"
end
def goal
<<-"EOF"
#{' ' * (now_position - 8)} |>
#{' ' * (now_position - 8)} ○ノ|
#{' ' * (now_position - 7)}\/|
#{'_' * (now_position - 7)}\/ >__
EOF
end
def failed
<<-"EOF"
#{adjust_str(' ', '', 7, ' ')}
#{adjust_str(' ', '', 7, '|>')}
#{adjust_str('_', ' _| ̄|○_', 8, '|_')}
EOF
end
def hashiru
effect_line = self.progress_count % 2 == 0 ? '==' : ' '
<<-"EOF"
#{adjust_str(' ', effect_line + ' ヘ○ノ', 7, ' ')}
#{adjust_str(' ', effect_line + ' ノ ', 7, '|>')}
#{adjust_str('_', effect_line + '/>_', 9, '|_')}
EOF
end
def tomaru
<<-"EOF"
#{adjust_str(' ', ' ヘ○ヘ', 7, '')}
#{adjust_str(' ', ' |∧ ', 7, '|>')}
#{adjust_str('_', '/__', 7, '|_')}
EOF
end
def korobu
<<-"EOF"
#{adjust_str(' ', '', 7, '')}
#{adjust_str(' ', '', 7, '|>')}
#{adjust_str('_', ' ヽ_○ノ ', 9, '|_')}
EOF
end
def adjust_str(padstr, printstr, rpad, endstr)
rpad_length = self.max_length - (printstr.length + now_position + rpad)
if rpad_length < 0
"#{padstr * (now_position + rpad_length)}#{printstr}#{endstr}"
else
"#{padstr * now_position}#{printstr}#{padstr * rpad_length}#{endstr}"
end
end
end
旗が端っこに置いてあるので、
テストが成功すると走って、失敗すると転んで、pending すると止まって、見事全テストが成功すると(pending は OK)旗を GET、失敗すると旗を GET できません。
ちょっと何言ってるかわからないと思うので、実行してみます。
走ります
走ります
止まります
再び走ります
転びます
走ります
はい、失敗
できたー。
ちなみにテストが成功したバージョン
力技でやっているので、あんまり詳しいコードの説明はしないですが、
IO.console.winsize.last
で console の幅を取得して、@example_count
に合計テスト数を持っているので、success_count
を保持しておいて、now_position
で走った位置を計算しています。
back_pointer(str)
は pointer の位置を元に戻して、同じ場所に出力する様にしています。
あとはゴリゴリやっているだけです。
もっと豪勢な絵になる予定だったのに、、、
あと、終わったら音出すとかもいいなあ。
おわりに
どうでしたか?ぼくは Fuubar に速攻戻しますが、みなさんも是非自作して見て下さい (それにしても AA って作るのめちゃくちゃ難しいですね...)