バッチファイルなどの運用スクリプトの単体テストにserverspecを使ってみた

# はじめに

スクリプトの単体テストにServerspecを使ってみました。

# バージョン

記事作成で用いたOS・モジュールのバージョンはこちらです。

- Windows 7 Pro SP1 32bit 日本語版
- ruby 2.2.3p173 (2015-08-18 revision 51636) [i386-mingw32]
- rspec (3.4.0)
- rspec-core (3.4.1)
- rspec-its (1.2.0)
- serverspec (2.26.0)
- specinfra (2.47.0)

※rubyスクリプトは全て文字コードを「UTF-8」で保存しています。
※バッチファイルは「Shift-JIS」で保存しています。

# テスト対象スクリプト

例として、次のバッチファイルに対してブラックボックステストを実施したいと思います。

```bat:C：\job\script\test.cmd
@ECHO OFF
set PATH_D_SCR=%~dp0
set PATH_D_SCR=%PATH_D_SCR:~0,-1%
set MY_NAME=%~n0
set MY_EXTENTION=%~x0
set MY_NAME_EXT=%MY_NAME%%MY_EXTENTION%
set ERR_NO=%1
If "%ERR_NO%" == "" (
    set ERR_NO=200
)
ruby %PATH_D_SCR%\ruby\test.rb %ERR_NO%
set RET_CD=%ERRORLEVEL%
If %RET_CD% NEQ 0 (
    If %RET_CD% NEQ 10 (
        Echo [%MY_NAME_EXT%] エラーが発生しました。：%RET_CD%
        set RET_CD=20
        GOTO :END
    ) Else (
        Echo [%MY_NAME_EXT%] 警告が発生しました。：%RET_CD%
        set RET_CD=10
        GOTO :END
    )
) Else (
    Echo [%MY_NAME_EXT%] 正常終了しました。：%RET_CD%
    set RET_CD=0
    GOTO :END
)
:END
exit %RET_CD%
```

バッチファイルから呼ばれるコマンドです。

```ruby:C：\job\script\ruby\test.rb
#!/usr/local/bin/ruby
i = ARGV[0]
i = 0   if i.nil?
puts "===<<< TEST.RB -> EXIT(#{i}) >>>==="
exit("#{i}".to_i)
```

# SpecInfraの修正

SpecInfraを少しだけ修正します。

```diff:lib\specinfra\backend\cmd.rb
13a14,17
>
>         # MODIFIED
>         script << "; exit $LASTEXITCODE"
>
28a33,36
>
>           # MODIFIED
>           status = status.exitstatus
>
45,46c53,60
<         architecture = @example.metadata[:architecture] || get_config(:architecture)
<
---
>         # MODIFIED
>         # architecture = @example.metadata[:architecture] || get_config(:architecture)
>         if @example.respond_to?(:metadata)
>           architecture = @example.metadata[:architecture]
>         else
>           architecture = get_config(:architecture)
>         end
>         architecture = Specinfra.configuration.architecture if architecture.nil? && ! Specinfra.configuration.architecture.nil?
```

# テストケースの作成

spec_helper.rbで「serverspec」を指定します。

```ruby:C：\job\test\spec\spec_helper.rb
require 'serverspec'
RSpec.configure do |config|
  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end
  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end
end
```

テストケースを作成します。

```ruby:C：\job\test\spec\test_spec.rb
require "spec_helper"

set :backend, :cmd
set :os, :family => 'windows'

Specinfra.configuration.architecture = :i386

CMD_PATH = "C:\\job\\script\\test.cmd"

context "正常系の確認".encode("Windows-31J") do
  describe command("#{CMD_PATH} 0") do
    its(:stdout) { should match /#{"正常終了しました。".encode("Windows-31J")}/ }
    its(:stdout) { should match /EXIT\(0\)/ }
    its(:exit_status) { should eq 0 }
  end
end

context "警告系の確認".encode("Windows-31J") do
  describe command("#{CMD_PATH} 10") do
    its(:stdout) { should match /#{"警告が発生しました。".encode("Windows-31J")}/ }
    its(:stdout) { should match /EXIT\(10\)/ }
    its(:exit_status) { should eq 10 }
  end
end

context "異常系の確認".encode("Windows-31J") do
  describe command("#{CMD_PATH} 20") do
    its(:stdout) { should match /#{"エラーが発生しました。".encode("Windows-31J")}/ }
    its(:stdout) { should match /EXIT\(20\)/ }
    its(:exit_status) { should eq 20 }
  end
end
```

# テストケースの実行

それでは単体テストを実行してみます。

```serverspec
PS C:\job\test> rspec -fd .\spec\cmd\test_spec.rb

正常系の確認
  Command "C:\job\script\test.cmd 0"
    stdout
      should match /正常終了しました。/
    stdout
      should match /EXIT\(0\)/
    exit_status
      should eq 0

警告系の確認
  Command "C:\job\script\test.cmd 10"
    stdout
      should match /警告が発生しました。/
    stdout
      should match /EXIT\(10\)/
    exit_status
      should eq 10

異常系の確認
  Command "C:\job\script\test.cmd 20"
    stdout
      should match /エラーが発生しました。/
    stdout
      should match /EXIT\(20\)/
    exit_status
      should eq 20

Finished in 2.31 seconds (files took 1.14 seconds to load)
9 examples, 0 failures

PS C:\job\test>
```

問題なく日本語での標準出力メッセージ判定と戻り値判定ができました。