ruby(1.9.3)+rspec(2.11)カスタムフォーマッター作ってみた

# | まえおき

本記事はRspecのカスタムフォーマッタークラスの作成に関する記事です。

# | きっかけ

むかしむかしrubyのバージョンを1.8.7から1.9.2へアップしたいおじいさんがおりました。

おじいさんはGemfileやらDockerやらをこねくりまわして、どうにか新しいバージョンでrspecが動くところまでこぎつけましたが、その時点でrspecのエラー数は1000件を超えておったそうな。。。

これは長丁場になると見込んだおじいさんはグーグルスプレッドシートを使ってエラーの一覧を管理しようと考えましたが、1000件を越えるエラーの内容をスプレッドシートに記帳するのは大変な作業です。

おじいさんは、だめもとで飼犬のポチオウにこのことを相談してみました。

するとポチオウは言いました。「おじいさんおじいさん、RspecのエラーをTSVファイルに書き出しするRspecのカスタムフォーマッタークラスを作ればいいんじゃない？」

# | バージョン

- ruby 1.9.3
- rails 3.0.1
- rspec 2.11.0

# | rspecはこんな感じで実行する

```
$ bundle exec rspec -r ./spec/formatter/documentation_with_write_tsv_formatter.rb --format DocumentationWithWriteTsvFormatter
```

- オプション補足
  - -r: 実行時に読み込み(require)されるファイルの指定
  - --format: 使用するFORMATTERを指定するオプション

# | 生成されるファイルはこんな感じ

## パス

tmp/spec_failure.tsv

## 仕様

- 区切り: タブ形式
- 改行: LF
- 文字列内の「"」は「””」に変換（スプレッドシートでのエスケープ指定）

## 内容はこんな感じ


|No.|コマンド|テスト概要|Failure/Error|エラー種別|エラー内容|バックトレース|処理開始日時|処理終了日時|処理時間(s)|
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
|1|rspec ./spec/models/user_spec.rb:72|User.create 新規作成 データが一件登録される|test = 12|TypeError|can't convert nil into Integer|./app/models/user.rb:24:in `block (2 levels) in create'|2019-10-31 18:22:03 +0900|2019-10-31 18:22:04 +0900|1.3508351

# | カスタムフォーマッタークラスはこんな感じ

## どうやって作ったか

ググってもあまり情報がなかったので、coreクラスのソースファイルを照会しながらなんとか実装したんだとさ。

[RSpec::Core::Formatters::BaseFormatter](https://github.com/rspec/rspec-core/blob/2-11-maintenance/lib/rspec/core/formatters/base_formatter.rb)
[RSpec::Core::Formatters::BaseTextFormatter](https://github.com/rspec/rspec-core/blob/2-11-maintenance/lib/rspec/core/formatters/base_text_formatter.rb)
[RSpec::Core::Formatters::DocumentationFormatter](https://github.com/rspec/rspec-core/blob/2-11-maintenance/lib/rspec/core/formatters/documentation_formatter.rb)

## 設置場所

特に決まりはないみたいなので「spec/formatter」のディレクトリを作成してその中に格納しましたとさ。

spec/formatter/documentation_with_write_tsv_formatter.rb

## ソースコード

RSpec::Core::Formatters::DocumentationFormatterを拡張しましたとさ。

```rb
require 'rspec'
require 'rspec/core/formatters/documentation_formatter'

class DocumentationWithWriteTsvFormatter < RSpec::Core::Formatters::DocumentationFormatter
  EOL_LF = "\n"
  HEADER = [
    "No.",
    "コマンド",
    "テスト概要",
    "Failure/Error",
    "エラー種別",
    "エラー内容",
    "バックトレース",
    "処理開始日時",
    "処理終了日時",
    "処理時間(s)"
  ]

  def initialize(output)
    super(output)
    @group_level = 0
    @failure_list = []
  end

  def failure_output(example, exception)
    cmd = "rspec #{RSpec::Core::Metadata::relative_path(example.location)}" # rspecコマンド(行数つき)
    failed_line = read_failed_line(exception, example).strip # fail発生箇所のソース
    type = exception.class.name # fail発生箇所のソース
    full_description = example.full_description # テストの条件
    backtrace_text = format_backtrace(exception.backtrace, example).join(EOL_LF) # トレースバック
    @failure_list.push [next_failure_index, cmd, full_description, failed_line, type, exception.message, backtrace_text, example.execution_result[:started_at], example.execution_result[:finished_at], example.execution_result[:run_time]] # エラー内容を配列に格納

    red("#{current_indentation}#{example.description.strip} (FAILED - #{@next_failure_index})") # エラー出力も行う
  end

  def close
    output_file()
  end

  private
    def output_file
      if @failure_list.count > 0
        FileUtil.fwrite "#{::Rails.root}/tmp/spec_failure.tsv", text_convert_failure_list, false
      end
    end

    def text_convert_failure_list
      all_text = ""

      @failure_list.unshift HEADER
      @failure_list.each do |failure|
        output = failure.map {|text|
          # 文字列の場合は「”」を「""」に変換(スプレッドシート向けのエスケープ)
          if text.kind_of?(String)
            text = text.to_s.gsub('"', '""') # 
            text = "\"#{text}\""
          end
          text
        }
        array_string = output.join("\t")
        all_text << "#{array_string}#{EOL_LF}"
      end

      all_text
    end
end
```

# 使いかた

冒頭に記載したようにrspecコマンドにオプションを加えて実行すれば、tmp/spec_failure.tsvにエラー内容が書き込まれるそうな。

```
$ bundle exec rspec -r ./spec/formatter/documentation_with_write_tsv_formatter.rb --format DocumentationWithWriteTsvFormatter
```

おじいさんは「Jenkinsおじさん使い」だったそうで、作成されたファイルをジョブの成果物として結果画面から簡単にダウンロードできるようしたそうな。

グーグルスプレッドシートへの記帳も楽々だったらしく、その後は末長くポチオウと暮らしましたそうな。

おしまい

# おわりに

少しでも楽しく書けるように昔話風にしてみました。

# 参考URL

- RSpec::Core::Formatters
  - [BaseFormatter](https://github.com/rspec/rspec-core/blob/2-11-maintenance/lib/rspec/core/formatters/base_formatter.rb)
  - [BaseTextFormatter](https://github.com/rspec/rspec-core/blob/2-11-maintenance/lib/rspec/core/formatters/base_text_formatter.rb)
  - [DocumentationFormatter](https://github.com/rspec/rspec-core/blob/2-11-maintenance/lib/rspec/core/formatters/documentation_formatter.rb)
- [Rspec3 カスタムフォーマットの例（CSV出力）](https://qiita.com/Kentakuma/items/9fac1b28945ccd20f8b3)