Aruba とは?
ArubaはCucumber、RSpec、Minitestのような人気のあるTDD/BDDフレームワークで
コマンドラインアプリケーションのテストを簡単で楽しいものにする拡張です。
特徴
- どんな言語で実装されたコマンドラインツールでもテスト可能
- テスト自体はRubyで書くが、テスト対象は Python の CLI ツールでも Golang の CLI ツールでもよい
- ファイルシステムやプロセス環境をヘルパーによって操作できる
- 例えば read でファイルを読み込みできる
- 例えば run で外部コマンドを実行し、その結果を have_output matcher などで検証できる
- ファイルシステムやプロセス環境はテストのたびにリセットされるので、 leaking state がない
- 例えばテスト中に作成されたファイルはテスト終了後には消えている
- コミュ二ティーサポートが手厚い
- ドキュメント にあるとおりに動作することが期待できる
フォーカス
gem install <your-gem>
のようなインストールや、ツールのデプロイ自体については
Arubaのフォーカス外とする。
サンプル
fizzbuzz を実行する CLI ツールを作成します。
準備
サンプルプロジェクトを準備します
- プロジェクトを作成
$ bundle gem -t rspec --exe fizzbuzz
- gemspec に thor と aruba を追加する
fizzbuzz.gemspec
# 略
spec.add_runtime_dependency 'thor'
spec.add_development_dependency "aruba"
# 略
- 依存パッケージをインストールする
$ bundle install --path vendor/bundle
- FizzBuzzの実装 lib/fizzbuzz.rb
lib/fizzbuzz.rb
require 'fizzbuzz/version'
require 'cli'
# FizzBuzz
#
# FizzBuzzの計算を行う
module Fizzbuzz
module_function
def fizzbuzz(limit)
limit_number = Integer(limit)
(0..limit_number).map do |num|
if (num % 15).zero? then 'FizzBuzz'
elsif (num % 5).zero? then 'Buzz'
elsif (num % 3).zero? then 'Fizz'
else num.to_s
end
end
end
end
- FizzBuzzの実装 lib/cli.rb
lib/cli.rb
require 'thor'
# Fizzbuzz::Models
module Fizzbuzz
# CLI
#
# FizzBuzzのコマンドライン
class CLI < Thor
class_option :help, type: :boolean, aliases: '-h', desc: 'help message.'
class_option :version, type: :boolean, desc: 'version'
class_option :debug, type: :boolean, aliases: '-d', desc: 'debug mode'
desc 'fizzbuzz', 'Get fizzbuzz result from limit number'
def fizzbuzz(limit)
print Fizzbuzz.fizzbuzz(limit).join(',')
exit
rescue => e
output_error_if_debug_mode(e)
exit(false)
end
desc 'version', 'version'
def version
puts Fizzbuzz::VERSION
end
private
def output_error_if_debug_mode(e)
return unless options[:debug]
STDERR.puts(e.message)
STDERR.puts(e.backtrace)
end
end
end
- FizzBuzzの実装 exe/fizzbuzz
exe/fizzbuzz
#!/usr/bin/env ruby
require "fizzbuzz"
Fizzbuzz::CLI.start(ARGV)
- FizzBuzzのテストの実装 spec/fizzbuzz_spec.rb
spec/fizzbuzz_spec.rb
require 'spec_helper'
describe Fizzbuzz do
it 'has a version number' do
expect(Fizzbuzz::VERSION).not_to be nil
end
context '.fizzbuzz' do
it "正常系" do
expect(%w(FizzBuzz 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz)).to eq(Fizzbuzz.fizzbuzz("15"))
end
it "数値以外の入力" do
expect { Fizzbuzz.fizzbuzz("a") }.to raise_error(ArgumentError)
end
end
end
Aruba RSpecのセットアップ
ArubaはCucumber, RSpec, Minitest に対応しています。
今回はRSpec版を利用します。
$ bundle exec aruba init --test-framework rspec
append spec/spec_helper.rb
identical spec/support/aruba.rb
append Gemfile
- Gemfile に aruba が追加されます
Gemfile
source 'https://rubygems.org'
gemspec
gem 'aruba', '~> 0.14.2'
- spec/support/aruba.rb が追加されます
spec/support/aruba.rb
require 'aruba/rspec'
- spec/spec_helper.rb に以下が追記されます
(require 'fizzbuzz'だけ手動で追記しました)
spec/spec_helper.rb
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
require 'fizzbuzz'
if RUBY_VERSION < '1.9.3'
::Dir.glob(::File.expand_path('../support/*.rb', __FILE__)).each { |f| require File.join(File.dirname(f), File.basename(f, '.rb')) }
::Dir.glob(::File.expand_path('../support/**/*.rb', __FILE__)).each { |f| require File.join(File.dirname(f), File.basename(f, '.rb')) }
else
::Dir.glob(::File.expand_path('../support/*.rb', __FILE__)).each { |f| require_relative f }
::Dir.glob(::File.expand_path('../support/**/*.rb', __FILE__)).each { |f| require_relative f }
end
- spec/cli_spec.rb
以下のコードが Aruba のテストコードです
spec/cli_spec.rb
require 'spec_helper'
RSpec.describe 'fizzbuzz command', type: :aruba do
context 'version option' do
before(:each) { run_command ('fizzbuzz v') }
it { expect(last_command_started).to be_successfully_executed }
it { expect(last_command_started).to have_output("0.1.0") }
end
context 'help option' do
expected = <<~EXPECTED
Commands:
fizzbuzz fizzbuzz # Get fizzbuzz result from limit number
fizzbuzz help [COMMAND] # Describe available commands or one specific command
fizzbuzz version # version
Options:
-h, [--help], [--no-help] # help message.
[--version], [--no-version] # version
-d, [--debug], [--no-debug] # debug mode
EXPECTED
before(:each) { run_command ('fizzbuzz help') }
it { expect(last_command_started).to be_successfully_executed }
it { expect(last_command_started).to have_output(expected) }
end
context 'fizzbuzz subcommand' do
expected = %w(FizzBuzz 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz).join(',')
before(:each) { run_command ('fizzbuzz fizzbuzz 15') }
it { expect(last_command_started).to be_successfully_executed }
it { expect(last_command_started).to have_output(expected) }
end
context 'fizzbuzz subcommand invalid args' do
before(:each) { run_command ('fizzbuzz fizzbuzz a') }
it { expect(last_command_started).not_to be_successfully_executed }
end
end
-
to be_successfully_executed
- status 0 で終了していることを確認
-
not_to be_successfully_executed
- status 0 以外で終了していることを確認
-
to have_output(contents)
- 出力が contents であることを確認。正規表現も使用可能
実行
$ bundle exec rspec spec/cli_spec.rb
fizzbuzz command
version option
should be successfully executed
should have output: "0.1.0"
help option
should be successfully executed
should have output: "Commands:\n fizzbuzz fizzbuzz # Get fizzbuzz result from limit number\n fizzbuzz help [COMM...e.\n [--version], [--no-version] # version\n -d, [--debug], [--no-debug] # debug mode\n"
fizzbuzz subcommand
should be successfully executed
should have output: "FizzBuzz,1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz"
fizzbuzz subcommand invalid args
should not be successfully executed
Finished in 3.1 seconds (files took 0.2151 seconds to load)
7 examples, 0 failures
参考
Rubyの静的解析ツール RubyCritic で Aruba + Cucumber を利用しています。
tbpgr/fizzbuzz
今回実装したサンプルプログラムは以下にupしてあります