Serverspecでよく使うテストの書き方まとめ

# はじめに
インフラ周りのテストにServerspecを使ってますが、よく書くテストの書き方がある程度パターン化してきたので、コピペで使えるサンプルの例文集としてまとめておきます。
この記事の目的は特に機能の網羅性をカバーしようというわけではありません。よくあるユースケースとして、Serverspecでこーゆーのどう書くの？と思ったときのスニペットとしてご利用下さい。

# 基本
## インストール関連
### パッケージがインストールされているか確認する
```ruby
describe package('git') do
  it { should be_installed }
end      
```

### 複数のパッケージがインストールされているかまとめて確認する
```ruby
%w{autoconf bison flex gcc gcc-c++ kernel-devel make m4}.each do |pkg|
  describe package(pkg) do
    it { should be_installed }
  end
end
```

### 指定のバージョンのパッケージがインストールされているか確認する
```ruby
describe package('td-agent') do
  it { should be_installed.with_version('1') }
end
```
上記はメジャーバージョンだけ指定した場合だけど、細かいバージョンも指定できる。

### gemとして指定のバージョンがインストールされているか確認する
```ruby
describe package('bundler') do
  it { should be_installed.by('gem').with_version('1.10.5') }
end
```

### fluentプラグインなど組込gemとしてインストールされているか確認する
```ruby
plugins = %w(config-expander datacounter numeric-counter zabbix forest)
plugins.each do |plugin|
  describe package("fluent-plugin-#{plugin}") do
    let(:path) { '/usr/lib64/fluent/ruby/bin:$PATH' }
    it { should be_installed.by(:gem) }
  end
end
```

## 汎用コマンドによる確認
### コマンドの標準出力から指定のバージョンがインストールされているか確認する
```ruby
describe command('ruby -v') do
  its(:stdout) { should match /ruby 2\.1\.4/ }
end
```

### コマンドのリターンコードからパスが通っているか確認する
```ruby
describe command('which mysql') do
  its(:exit_status) { should eq 0 }
end
```

### sudoせずコマンドを実行する
```ruby
describe command('which mysql') do
  let(:disable_sudo) { true }
  its(:exit_status) { should eq 0 }
end
```

### sudoするユーザを指定する
```ruby
describe command('which mysql') do
  let(:sudo_options) { '-u user01 -i'}
  its(:exit_status) { should eq 0 }
end
```

## サービスの起動確認
### 指定のサービスが起動していて自動起動設定されているか確認する
```ruby
describe service('elasticsearch') do
  it { should be_enabled }
  it { should be_running }
end
```

### 指定のポートをListenしているか確認する
```ruby
describe port("9200") do
  it { should be_listening }
end
```

### curlでHTTPアクセスして200 OKが返ってくるか確認する
```ruby
describe command('curl http://127.0.0.1:9200/_plugin/head/ -o /dev/null -w "%{http_code}\n" -s') do
  its(:stdout) { should match /^200$/ }
end
```

## ユーザとグループ
### グループが存在するか確認する
```ruby
describe group('ec2-user') do
  it { should exist }
end
```

### ユーザが指定のグループに所属しているか確認する
```ruby
describe user('ec2-user') do
  it { should belong_to_group 'ec2-user' }
end
```

### ユーザが指定のUIDを持っているか確認する
```ruby
describe user('td-agent') do
  it { should have_uid 403 }
end
```

## ファイルを確認する
### ファイルの中身が指定の文字列にマッチするか確認する
```ruby
describe file('/etc/sysconfig/clock') do
  its(:content) { should match /ZONE="Asia\/Tokyo"/ }
end
```

### ファイルに読み込み権限があるか確認する
```ruby
%w{
  /var/log/httpd/access.log
  /var/log/httpd/error.log
}.each do |logfile|
  describe file(logfile) do
    it { should be_readable.by_user('td-agent') }
  end
end
```
### ディレクトリのオーナーとパーミッションを確認する
```ruby
home_dir = "/home/ec2-user"
describe file("#{home_dir}/.ssh") do
  it { should be_directory }
  it { should be_owned_by('ec2-user') }
  it { should be_grouped_into('ec2-user') }
  it { should be_mode '700' }
end
```

## その他
### 名前解決できるか確認する
```ruby
zabbix_host = 'zabbix'
describe host(zabbix_host) do
  it { should be_resolvable.by('hosts') }
end
```

# 応用的なトピック
## 動的な情報の取得
### 動的にテスト対象のホスト名を取得する
```ruby
hostname = host_inventory['hostname']
describe file('/etc/sysconfig/network') do
  its(:content) { should match /HOSTNAME=#{hostname}/ }
end
```

### RedHat系かどうか判定する
```ruby
if os[:family] == 'redhat'
  ...
else
  ...
end
```

elseがない場合は、以下のような書き方もできる

```ruby
describe yumrepo('epel'), :if => os[:family] == 'redhat' do
  it { should exist }
  it { should be_enabled }
end
```

### Amazon Linuxかどうか判定する
```spec_helper.rb
def os_platform_amazon?
  Specinfra.backend.run_command('uname -r').stdout.include?("amzn1")
end
```

```ruby
require 'spec_helper'

if os_platform_amazon?
  %w{aws-cli s3cmd}.each do |pkg|
    describe package(pkg) do
      it { should be_installed }
    end
  end
end
```
（2015/10/21追記） 現在の最新のserverspecでは`if os[:family] == 'amazon'` で判定できるようです。これはserverspecの基盤となっているspecinfra(v2.36.0)でos[:family]にamazonが判定できるようなったからで、バージョンの依存関係から対応するserverspec(v2.4.0)以降から使えるはず。

## テストケースの共有
### 複数のサーバ種別で共通するテストケースを使いたい
```shared/commons_examples.rb
require 'spec_helper'

shared_examples 'commons' do
  describe "commons spec" do
    describe package('git') do
      it { should be_installed }
    end
  end
end
```

```spec_helper.rb
base_spec_dir = Pathname.new(File.join(File.dirname(__FILE__)))
Dir[base_spec_dir.join('shared/**/*.rb')].sort.each{ |f| require f }

```

```webapp/app_server_spec.rb
require 'spec_helper'

describe "app server spec" do
  include_examples 'commons'
end
```

```webapp/web_server_spec.rb
require 'spec_helper'

describe "web server spec" do
  include_examples 'commons'
end
```

## テスト対象の制御
### テストケースをロール単位で管理したい
以前ブログに書いた以下の記事を参照↓
[Serverspecのテストケースをロール単位で管理する](http://d.hatena.ne.jp/minamijoyo/20150225/p1)

### テスト対象のIPとロールを指定する
以前ブログに書いた以下の記事を参照↓
[Serverspecでテスト対象のIPとロールを指定する](http://d.hatena.ne.jp/minamijoyo/20150301/p1)

## カスタムマッチャを作りたい
### YAMLとしてパース出来るかどうかテストしたい
以前Qiitaに書いた以下の記事を参照↓
[serverspecでカスタムマッチャを定義してYAML形式としてパース出来るかどうかをテストするbe_yamlを作ってみる](http://qiita.com/minamijoyo/items/1a49ac063ecbd1e4f6aa)


# おわりに
書き出してみたら色々あったけど、何か忘れてるものあったらor新しい小技を覚えたら、随時追記します。