Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

はじめに

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

基本

インストール関連

パッケージがインストールされているか確認する

describe package('git') do
  it { should be_installed }
end      

複数のパッケージがインストールされているかまとめて確認する

%w{autoconf bison flex gcc gcc-c++ kernel-devel make m4}.each do |pkg|
  describe package(pkg) do
    it { should be_installed }
  end
end

指定のバージョンのパッケージがインストールされているか確認する

describe package('td-agent') do
  it { should be_installed.with_version('1') }
end

上記はメジャーバージョンだけ指定した場合だけど、細かいバージョンも指定できる。

gemとして指定のバージョンがインストールされているか確認する

describe package('bundler') do
  it { should be_installed.by('gem').with_version('1.10.5') }
end

fluentプラグインなど組込gemとしてインストールされているか確認する

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

汎用コマンドによる確認

コマンドの標準出力から指定のバージョンがインストールされているか確認する

describe command('ruby -v') do
  its(:stdout) { should match /ruby 2\.1\.4/ }
end

コマンドのリターンコードからパスが通っているか確認する

describe command('which mysql') do
  its(:exit_status) { should eq 0 }
end

sudoせずコマンドを実行する

describe command('which mysql') do
  let(:disable_sudo) { true }
  its(:exit_status) { should eq 0 }
end

sudoするユーザを指定する

describe command('which mysql') do
  let(:sudo_options) { '-u user01 -i'}
  its(:exit_status) { should eq 0 }
end

サービスの起動確認

指定のサービスが起動していて自動起動設定されているか確認する

describe service('elasticsearch') do
  it { should be_enabled }
  it { should be_running }
end

指定のポートをListenしているか確認する

describe port("9200") do
  it { should be_listening }
end

curlでHTTPアクセスして200 OKが返ってくるか確認する

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

ユーザとグループ

グループが存在するか確認する

describe group('ec2-user') do
  it { should exist }
end

ユーザが指定のグループに所属しているか確認する

describe user('ec2-user') do
  it { should belong_to_group 'ec2-user' }
end

ユーザが指定のUIDを持っているか確認する

describe user('td-agent') do
  it { should have_uid 403 }
end

ファイルを確認する

ファイルの中身が指定の文字列にマッチするか確認する

describe file('/etc/sysconfig/clock') do
  its(:content) { should match /ZONE="Asia\/Tokyo"/ }
end

ファイルに読み込み権限があるか確認する

%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

ディレクトリのオーナーとパーミッションを確認する

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

その他

名前解決できるか確認する

zabbix_host = 'zabbix'
describe host(zabbix_host) do
  it { should be_resolvable.by('hosts') }
end

応用的なトピック

動的な情報の取得

動的にテスト対象のホスト名を取得する

hostname = host_inventory['hostname']
describe file('/etc/sysconfig/network') do
  its(:content) { should match /HOSTNAME=#{hostname}/ }
end

RedHat系かどうか判定する

if os[:family] == 'redhat'
  ...
else
  ...
end

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

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
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のテストケースをロール単位で管理する

テスト対象のIPとロールを指定する

以前ブログに書いた以下の記事を参照↓
Serverspecでテスト対象のIPとロールを指定する

カスタムマッチャを作りたい

YAMLとしてパース出来るかどうかテストしたい

以前Qiitaに書いた以下の記事を参照↓
serverspecでカスタムマッチャを定義してYAML形式としてパース出来るかどうかをテストするbe_yamlを作ってみる

おわりに

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

crowdworks
21世紀の新しいワークスタイルを提供する日本最大級のクラウドソーシング「クラウドワークス」のエンジニアチームです!
https://crowdworks.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした