Cookbookテストフレームワーク「ChefSpec」 #opschef_ja
この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。
ChefSpecはCookbookテストフレームワークです。RSpecを用いたテスト駆動開発(TDD, Test Driven Development)と呼ばれる開発手法のためのテストフレームワークで、まずテストを書き、次のそのテストをパスするコードを書き、それらを繰り返して開発を進めていくという手順を取ります。実際にCookbookをノードに適用せず、Cookbookが期待した動作を行うように記述されているかどうかをテストします。
ChefSpecのインストール
gemでインストールが可能です。
なお、以前インストールできるChefSpec 0.9.0はChef 11には対応していなかったので、インストールオプションに--preをつけるか、--version '1.0.0.rc1'をつけてChefSpec 1.0.0.rc1をインストールする必要がありました(Error in running spec and using knife in chef 11.2 and chefspec 0.9.0, Add support for Chef 11)。2013年4月23日現在、ChefSpec 1.0.0が正式にリリースされたため、特にオプション指定なしでChef 11に対応したChefSpec 1.0.0がインストールできます。
ubuntu@kitchen:~$ sudo /opt/chef/embedded/bin/gem install chefspec --no-rdoc --no-ri --verbose : Successfully installed chefspec-1.0.0 10 gems installed ubuntu@kitchen:~$
exampleと実コードの作成
ChefSpec (RSpec)ではテストケースのことを「example」と呼びます。
knife cookbookのサブコマンドcreate_specsでexampleの雛形が作成できます。
ubuntu@kitchen:~/chef-repo/cookbooks$ knife cookbook create apache2-take ** Creating cookbook apache2-take ** Creating README for cookbook: apache2-take ** Creating CHANGELOG for cookbook: apache2-take ** Creating metadata for cookbook: apache2-take ubuntu@kitchen:~/chef-repo/cookbooks$
ubuntu@kitchen:~/chef-repo/cookbooks$ knife cookbook create_specs apache2-take ** Creating specs for cookbook: apache2-take ubuntu@kitchen:~/chef-repo/cookbooks$
ubuntu@kitchen:~/chef-repo/cookbooks$ ls -la apache2-take/spec 合計 12 drwxr-xr-x 2 ubuntu ubuntu 4096 4月 17 10:53 . drwxr-xr-x 10 ubuntu ubuntu 4096 4月 17 10:53 .. -rw-r--r-- 1 ubuntu ubuntu 220 4月 17 10:53 default_spec.rb ubuntu@kitchen:~/chef-repo/cookbooks$
ubuntu@kitchen:~/chef-repo/cookbooks$ cat apache2-take/spec/default_spec.rb
require 'chefspec'
describe 'apache2-take::default' do
let (:chef_run) { ChefSpec::ChefRunner.new.converge 'apache2-take::default' }
it 'should do something' do
pending 'Your recipe examples go here.'
end
end
ubuntu@kitchen:~/chef-repo/cookbooks$
このファイルにexampleを書いていきます。
例えば、apache2 git-core curl unzip の4パッケージをインストールするようなCookbookになっていることを期待するexampleは次のようになります。もちろん、Rubyの文法を用いて記載できます。
ubuntu@kitchen:~/chef-repo/cookbooks$ vi apache2-take/spec/default_spec.rb
require 'chefspec'
describe 'apache2-take::default' do
let (:chef_run) { ChefSpec::ChefRunner.new.converge 'apache2-take::default' }
%w{ apache2 git-core curl unzip }.each do |s|
it "install #{s}" do
chef_run.should install_package s
end
end
end
ubuntu@kitchen:~/chef-repo/cookbooks$
rspecコマンドを実行してみます。当然、exampleに対応した実コードが書かれていないのでテストは失敗します。
ubuntu@kitchen:~/chef-repo/cookbooks$ rspec -fd --color apache2-take apache2-take::default install apache2 (FAILED - 1) install git-core (FAILED - 2) install curl (FAILED - 3) install unzip (FAILED - 4) Failures: 1) apache2-take::default install apache2 Failure/Error: chef_run.should install_package s No package resource named 'apache2' with action :install found. # ./apache2-take/spec/default_spec.rb:8:in `block (3 levels) in <top (required)>' 2) apache2-take::default install git-core Failure/Error: chef_run.should install_package s No package resource named 'git-core' with action :install found. # ./apache2-take/spec/default_spec.rb:8:in `block (3 levels) in <top (required)>' 3) apache2-take::default install curl Failure/Error: chef_run.should install_package s No package resource named 'curl' with action :install found. # ./apache2-take/spec/default_spec.rb:8:in `block (3 levels) in <top (required)>' 4) apache2-take::default install unzip Failure/Error: chef_run.should install_package s No package resource named 'unzip' with action :install found. # ./apache2-take/spec/default_spec.rb:8:in `block (3 levels) in <top (required)>' Finished in 0.00518 seconds 4 examples, 4 failures Failed examples: rspec ./apache2-take/spec/default_spec.rb:7 # apache2-take::default install apache2 rspec ./apache2-take/spec/default_spec.rb:7 # apache2-take::default install git-core rspec ./apache2-take/spec/default_spec.rb:7 # apache2-take::default install curl rspec ./apache2-take/spec/default_spec.rb:7 # apache2-take::default install unzip ubuntu@kitchen:~/chef-repo/cookbooks$
このexampleを満たすようにRecipeを書きます。
ubuntu@kitchen:~/chef-repo/cookbooks$ vi apache2-take/recipes/default.rb
package 'apache2'
package 'git-core'
package 'curl'
package 'unzip'
ubuntu@kitchen:~/chef-repo/cookbooks$
rspecコマンドを実行します。今度は実コードが存在しているため、テストが成功しました。
ubuntu@kitchen:~/chef-repo/cookbooks$ rspec -fd --color apache2-take apache2-take::default install apache2 install git-core install curl install unzip Finished in 0.00755 seconds 4 examples, 0 failures ubuntu@kitchen:~/chef-repo/cookbooks$
実コードは冗長なので、配列を用いて書き直してみます。
ubuntu@kitchen:~/chef-repo/cookbooks$ vi apache2-take/recipes/default.rb
%w{ apache2 git-core curl }.each do |s|
package s
end
ubuntu@kitchen:~/chef-repo/cookbooks$
rspecコマンドを実行します。実コードを変更した際にunzipが抜けていたため、テストが失敗しています。このように、リファクタリングを行った際のデグレードの発見も容易となります。
ubuntu@kitchen:~/chef-repo/cookbooks$ rspec -fd --color apache2-take apache2-take::default install apache2 install git-core install curl install unzip (FAILED - 1) Failures: 1) apache2-take::default install unzip Failure/Error: chef_run.should install_package s No package resource named 'unzip' with action :install found. # ./apache2-take/spec/default_spec.rb:8:in `block (3 levels) in <top (required)>' Finished in 0.00704 seconds 4 examples, 1 failure Failed examples: rspec ./apache2-take/spec/default_spec.rb:7 # apache2-take::default install unzip ubuntu@kitchen:~/chef-repo/cookbooks$
再び実コードを修正したことで、テストが成功しました。
ubuntu@kitchen:~/chef-repo/cookbooks$ vi apache2-take/recipes/default.rb
%w{ apache2 git-core curl unzip }.each do |s|
package s
end
ubuntu@kitchen:~/chef-repo/cookbooks$
ubuntu@kitchen:~/chef-repo/cookbooks$ rspec -fd --color apache2-take apache2-take::default install apache2 install git-core install curl install unzip Finished in 0.00758 seconds 4 examples, 0 failures ubuntu@kitchen:~/chef-repo/cookbooks$
最終的に以下のようなexampleができました。これを元に実コードを作っていきます。
require 'chefspec'
describe 'apache2-take::default' do
let (:chef_run) { ChefSpec::ChefRunner.new.converge 'apache2-take::default' }
%w{ apache2 git-core curl unzip }.each do |s|
it "install #{s}" do
chef_run.should install_package s
end
end
it 'start on boot' do
chef_run.should set_service_to_start_on_boot 'apache2'
end
describe 'change port' do
%w{ /etc/apache2/ports.conf /etc/apache2/sites-available/default }.each do |s|
it "create #{s}" do
chef_run.should create_file_with_content s, chef_run.node[ 'apache2-take' ][ 'port' ]
chef_run.template( s ).should be_owned_by( 'root', 'root' )
chef_run.template( s ).mode.should == 00644
chef_run.template( s ).should notify( 'service[apache2]', :restart )
end
end
end
describe 'set content' do
s = 'https://github.com/cl-lab-k/apache2-take-sample-page'
it "clone #{s}" do
chef_run.git( s )
end
it 'create /var/www/index.html' do
chef_run.execute( "cp -f #{Chef::Config[ :file_cache_path ]}/apache2-take-sample-page/index.html /var/www/index.html" )
end
end
describe 'setcontent (static)' do
it 'get apache2-take-sample-image.zip' do
chef_run.remote_file( "https://github.com/cl-lab-k/apache2-take-sample-image/archive/master.zip" )
end
it 'unzip apache2-take-sample-image.zip' do
chef_run.execute( "unzip -o -d #{Chef::Config[ :file_cache_path ]} #{Chef::Config[ :file_cache_path ]}/apache2-take-sample-image.zip" )
# not_if { ::File.exists?( "#{Chef::Config[ :file_cache_path ]}/apache2-take-sample-image-master" ) }
end
it 'move /var/www/img' do
chef_run.execute( "mv -f #{Chef::Config[ :file_cache_path ]}/apache2-take-sample-image-master /var/www/img" )
# not_if { ::File.exists?( '/var/www/img' ) }
end
end
it 'start apache2' do
chef_run.should start_service 'apache2-start'
end
end
以上がChefSpecの概略です。exampleの書き方についてはMaking Assertionsを参照してください。
ChefSpecは既にできている実コードに後付けすることも可能ですが、できるだけexampleを先に書き、実コードを後から作っていく形で開発を進めたほうがよいでしょう。