Madogiwa Blog

主に技術系の学習メモに使っていきます。

Markdown形式のテキストをRSpec形式に変換するGemを作りました💎

画面仕様書等、Markdownで整理していたものからRSpecを書き直すのが意外と手間と思うことがあったのでMarkdownで書いたものをRSpec形式のテキストに変換するGemを作りました。

github.com

使い方

使い方は、

$ gem install markdown_to_rspec
# CLI
$ markdown_to_rspec -f `MARKDOWN_FILE_PATH`
#=> return A string in RSpec format
$ markdown_to_rspec -t `MARKDOWN__TEXT`
#=> return A string in RSpec format

上記ようにgemをinstall後に引数に以下の値を渡すことでRSpec形式の文字列に変換します。

例えばこのような画面仕様書的なマークダウンを変換すると、

# Details.
A screen to check something
ref: https://example/com/tickets/1

## Initial Display.

### When a record exists.
* The title must be displayed.
* The text must be displayed.

### When the record does not exist.
* The title should not be displayed.
* The text should not be displayed.

### Other cases.
* 500 pages to be displayed.

# Index.
A screen to check something

## Initial Display.
* The items must be displayed.

以下のようなRSpec形式の文字列を取得出来ます✨

RSpec.describe 'Details.' do
  # A screen to check something
  # ref: https://example/com/tickets/1
  describe 'Initial Display.' do
    context 'When a record exists.' do
      it 'The title must be displayed.' do
      end
      it 'The text must be displayed.' do
      end
    end
    context 'When the record does not exist.' do
      it 'The title should not be displayed.' do
      end
      it 'The text should not be displayed.' do
      end
    end
    context 'Other cases.' do
      it '500 pages to be displayed.' do
      end
    end
  end
end
RSpec.describe 'Index.' do
  # A screen to check something
  describe 'Initial Display.' do
    it 'The items must be displayed.' do
    end
  end
end

※インデントの数やMarkdown#のどのレベルをdescribe or contextにする等は一旦固定値になっているので調整出来ません🙏💦

個人的な技術Topic

作った上で個人的な技術Topicをちょっと書いときます。

RDoc::Markdownを使ってマークダウンから中間オブジェクトを生成

MarkdownからRSpecに変換する際になんかしらの中間的なオブジェクトを生成する必要がありそうだなぁと思い、 なんかいい感じのgemを探していたのですが見つからず。。。

そういればRDocってMarkdown形式でかけるなと思い標準ライブラリを探していたら、 RDoc::Markdownがまさにな感じだったのでそれを使うことにしました。

docs.ruby-lang.org

下記のような感じでMarkdown形式の文字列をRDoc::Markdown.parseに渡してあげると、 RDoc::Markup::DocumentというMarkdownのアイテムの関係性を保持した中間のオブジェクトを返却してくれます。

require 'rdoc/markdown'

markdown = <<~MARKDOWN
# title
## subtitle
* item 1
* item 2
MARKDOWN

RDoc::Markdown.parse(markdown)
#=> #<RDoc::Markup::Document:0x00007f92f0b2e398 @parts=[#<struct RDoc::Markup::Heading level=1, text="title">, #<struct RDoc::Markup::Heading level=2, text="subtitle">, #<RDoc::Markup::List:0x00007f92f11dd928 @type=:BULLET, @items=[#<RDoc::Markup::ListItem:0x00007f92f0b65f78 @label=nil, @parts=[#<RDoc::Markup::Paragraph:0x00007f92f11e3828 @parts=["item 1"]>]>, #<RDoc::Markup::ListItem:0x00007f92f11e8fa8 @label=nil, @parts=[#<RDoc::Markup::Paragraph:0x00007f92f0b54b38 @parts=["item 2"]>]>]>], @file=nil, @omit_headings_from_table_of_contents_below=nil>

このオブジェクトをRSpecに変換するコードを今回は実装しています。

この辺のRDoc::Markup::Formatterを継承したクラスを作ったほうがいい感じに出来たのかも・・・? ※今回は使い方を学習するのが結構難しそうで自前で実装してしまった。。。

docs.ruby-lang.org

OptionParserを使ってCLIのインタフェースを定義

CLI部分のインタフェース部分を標準ライブラリのOptionParserを使って実装してます。

docs.ruby-lang.org

OptionParserを使うと下記のような感じで実行時に渡されたオプションに対して何を実行するのかを定義出来ます。

option.onで指定されたオプションと実行する処理を紐付けして、option.parse!(ARGV)で、 実行時引数を元に実行する処理を判定して引数をよしなに渡してくれるようです✨(便利)

require 'optparse'
require 'markdown_to_rspec/rake_tasks'

option = OptionParser.new
option.on('-f', '--file FILE_PATH', 'Set the target Markdown file path.') do |v|
  Rake::Task['markdown_to_rspec:to_rspec_from_file'].execute(file_path: v)
end
option.on('-v', '--version', 'Show gem version.') do
  Rake::Task['markdown_to_rspec:version'].execute
end
option.parse!(ARGV)

上記のような設定だと以下のような動きになります。

  • -f--formatの時に引数で渡された値を元にRake::Task['markdown_to_rspec:to_rspec_from_file']を実行 例) $ markdown_to_rspec -f sample.md
  • -v--versionの時にRake::Task['markdown_to_rspec:version']を実行 例) $ markdown_to_rspec -v

あとは--helpも自動で実装してくれます✨

$ markdown_to_rspec -h
Usage: markdown_to_rspec [options]
    -f, --file FILE_PATH             Set the target Markdown file path.
    -v, --version                    Show gem version.

おわりに

マークダウン形式で書かれた画面仕様書からRSpecのE2Eテスト等を作るときに、 抜け漏れ等を防げて便利な気がするので、せっかく作ったので使っていこうと思います💪

色々調べるとRubyの標準ライブラリ、便利ですね✨