fbpx

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を先に書き、実コードを後から作っていく形で開発を進めたほうがよいでしょう。

Author

Chef・Docker・Mirantis製品などの技術要素に加えて、会議の進め方・文章の書き方などの業務改善にも取り組んでいます。「Chef活用ガイド」共著のほか、Debian Official Developerもやっています。

Daisuke Higuchiの記事一覧

新規CTA