公私共にchefを使っていて、テストにchefspecを使っていますが、RSpec系になじみがないからか、なかなか覚えられないのでチートシートにまとめます。
詳細や正確な情報は本家で確認してください。
本家:https://github.com/acrmp/chefspec
specを書く上での準備
nodeの設定
cookbook, role, node等でattributesを定義していて、レシピの動作結果がこれに依存している場合は、convergeより先にnodeに値をセットする必要があります。
let( :chef_run ) do
chef_run = ChefSpec::ChefRunner.new do |node|
node.set['my_attribute'] = 'bar'
node.set['my_other_attribute'] = {'hoge' => 'bar2'}
end.converge 'example::default'
end
chef_environmentの設定
chef-soloではサポートしていなかったりで、個人的にはあまり使いたくないところですが。Environmentはインスタンスなのでnodeと比べると面倒です。
let( :chef_run ) do
ChefSpec::ChefRunner.new do |node|
env = Chef::Environment.new
env.name 'staging'
node.stub(:chef_environment).and_return env.name
Chef::Environment.stub(:load).and_return env
end.converge 'example::default'
end
cookbook_pathの追加
site-cookbookを使っていたりでcookbookpathが複数ある場合は、パスを追加する必要があります。
let (:chef_run) { ChefSpec::ChefRunner.new(:cookbook_path => ['cookbooks', 'site-cookbooks']).converge 'example::default' }
specの書き方
パッケージ、サービス関連
# package
expect(chef_run).to install_package 'foo'
expect(chef_run).to install_package_at_version 'foo', '1.2.3'
expect(chef_run).to upgrade_package 'foo'
expect(chef_run).to install_yum_package 'yum-foo'
# service
expect(chef_run).to set_service_to_start_on_boot 'food'
expect(chef_run).to restart_service 'food'
ファイル、ディレクトリ操作関連
執筆時点(2013/02)の現行バージョンでは、cookbook_fileがcreate_fileで認識されないのでご注意を。(pullreqは承認されているので、じきに反映されると思いますが)
私の勘違いでした。0.9.0でもcookbook_fileはサポートされています。
# directory
expect(chef_run).to create_directory '/var/lib/foo'
dir = chef_run.directory('/var/lib/foo')
expect(dir.mode).to eq "0700"
expect(dir).to be_owned_by('user', 'group')
# cookbook_file
expect(chef_run).to create_cookbook_file '/var/lib/hoge.txt'
file = chef_run.cookbook_file('/var/lib/hoge.txt')
expect(file.mode).to eq "0700"
expect(file).to be_owned_by('user', 'group')
# file (template)
expect(chef_run).to create_file '/var/log/bar.log'
file = chef_run.template('/var/log/bar.log')
expect(file.mode).to eq "0600"
expect(file).to be_owned_by('user', 'group')
# remote_file
expect(chef_run).to create_remote_file 'http://path/to/file'
file = chef_run.remote_file('/var/lib/hoge.txt')
expect(file.mode).to eq "0700"
expect(file).to be_owned_by('user', 'group')
# symlink
expect(chef_run).to create_link "/path/to/link"
chef_run.link("/path/to/link").to.should == "/path/from/link"
その他
# cron
expect(chef_run.cron('daily_job')).to be
# include recipe
expect(chef_run).to include_recipe 'foo::bar'
# if you want check xxx not, use shoud_not
expect(chef_run).not_to create_directory '/no/such/directory'
あとがき
テストの書き方やその度合いは、統一的な見解はないので人それぞれです。私の場合は、仕様として明らかにしたいもの、後続処理などに影響を与える重要なものに絞ってテストを書くようにしています。特に設計がまとまらない時はテストファーストをすることで、設計とテスト準備を同時にやっつけます。