The best way to develop chef cookbooks is by following the test driven development. Your cookbooks should be treated like application code. Cookbooks can be tested using tools like testkitchen, foodcritic etc. In this guide, we will walk you through the test driven cookbook development using testkitchen(integration testing) and chefspec(unit testing).
Chef Cookbook Testing Tutorial
Most of the tools associated with chef comes bundled with chef development kit (aka chefdk). If you don’t have the latest chefdk installed on your machine, following the steps given below for setting up the chef development environment.
Install Chef development kit
1. Install the latest chef development kit from here. https://downloads.chef.io/chef-dk/
2. Test the installation by verifying the Chef development kit version using the following command.
chef --version
Other than chef, there are a number of tools which get installed as the part if development kit. The output shows the versions of each one of those.
vagrant@mike:/vagrant/chef-repo/.chef$ chef --version Chef Development Kit Version: 0.8.1 chef-client version: 12.4.4 berks version: 3.3.0 kitchen version: 1.4.2 vagrant@mike:/vagrant/chef-repo/.chef$
Create a Repo
1. Create a repo to store the cookbooks, data bags and other chef related folders using the chef generate command.
chef generate repo chef-repo
chef-repo is the custom name given by the user and the name is relatively insignificant, but it should be meaningful to you.
Once you execute the above command, as a net result you will see a bunch of folders and files created inside the chef-repo folder.
Test Driven Workflow
In this guide, we will look into a test driven workflow wherein we will have a web server which says hello world when we pull up the web page.
First create a cookbook using the chef generate command. The name of the cookbook is arbitrary and it should mean something to you. In this example, we are going to call our cookbook “webserver”.
Note: Make sure you execute the command from the /chef-repo/cookbooks directory to create the cookbook in the cookbooks directory.
chef generate cookbook webserver
Now, we have a cookbook named webserver and now we will start with the integration test. In order to run this integration test, you need an environment to run out webserver recipe.
So, when it comes to the environment, one of the things that you can do is, you can use a tool called testkitchen. It allows you to spin up virtual machines where you can execute your recipes. One of the best ways you can use test kitchen is to use vagrant. If you do not have any idea about vagrant, you could use something like AWS ec2 or digital ocean to spin up virtual machines. In this example, we will stick with vagrant.
The environment details can set setup in our webserver cookbooks kitchen.yml file. If you open the webserver cookbook in sublime or any other text editor, you will find the kitchen.yml file in the webserver folder.
The kitchen.yml file for our testing looks like the following.
--- driver: name: vagrant provisioner: name: chef_zero platforms: - name: ubuntu-14.04 # - name: centos-7.1 suites: - name: default run_list: - recipe[webserver::default] attributes:
As you can see, I have commented out centos-7.1 under platforms because, we are going to test this only on ubuntu.
Execute the following kitchen command to list out all our default environment setup.
kitchen list
Example output:
Instance Driver Provisioner Verifier Transport Last Action default-ubuntu-1404 Vagrant ChefZero Busser Ssh
To launch the VM for our cookbook testing, just execute the following command. It will launch all the VM’s mentioned in the kitchen.yml file. In our case its just ubuntu. But you can mention as many platforms you want.
kitchen create
So the above one command could launch as many VM’s you want based on the kitchen configuration. Oncthe VM is created, you can run “kitchen list” command to check the created status.
It will take a while to launch the test vagrant VM. Once launched, you will have your test VM ready for integration testing.
Writing Integration Tests
When we generated the cookbook using chefdk, it automatically creates all the necessary test files. You can see a test folder will the following tree structure.
└── test └── integration ├── default │ └── serverspec │ └── default_spec.rb └── helpers └── serverspec └── spec_helper.rb
We will write all the tests in serverspec. Serverspec is built on top of ruby test framework rspec.
If you open default_spec.rb file, you will see the default skeleton for our web server cookbook. Explaining serverpsec is out of the scope of this article. Visit http://serverspec.org/resource_types.html for more details on server spec.
So our first test case is to test if the web server is present in the server or not. The test block looks like the following.
require 'spec_helper' describe 'webserver::default' do it 'displays the home page' do expect(command("wget http://localhost").exit_status).to eq 0 end end
Basically, the above code runs the curl command to localhost and equates the exit status to 0. If the exit status is 0, web server is present in the server.
This test should fail for us, because, we haven’t configured the web server yet.
Lets try running this failing test using test kitchen.
Execute the following command from webserver cookbook directory to set up chef-client and run our webserver cookbook on our test kitchen node we created earlier.
kitchen converge
The above command will return a successful chef-client run because our webserver cookbook does not have any resources as of now.
Now, let’s run our failing test using the following command. It will install all the necessary server spec gem to run the tests.
kitchen verify
Now, if you scroll up a bit and see the output, you would see 1 failure.
webserver::default displays the home page (FAILED - 1) Failures: 1) webserver::default displays the home page Failure/Error: expect(command("wget http://localhost").exit_status).to eq 0 expected: 0 got: 4
The next thing you would want to do is to write a test to check the apache package installation. It is good to put this in the unit test because we might write a cookbook to run in different platforms.
For the unit test, chefdk provides chefspec, a framework written on top of RSpec.
All the unit test will go under the spec directory.
├── spec │ ├── spec_helper.rb │ └── unit │ └── recipes │ └── default_spec.rb
Open the default_spec.rb file and copy and add the following chefspec test for checking the apache package installation code to the default_spec.rb file.
it 'installs apche' do expect(chef_run).to install_package "apache2" end
To run this test, execute the rspec on the spec directory as shown below.
chef exec rspec spec
Now you will see the failed test for apache package. Now we have good failing tests. Let’s start writing our recipes to make these test pass.
open the default.rb recipe and add the apache package recipe.
package "apache2" do action :install end
Now, if you run the unit test, it will pass.
Lets try running our integration test and run the test on a real machine. Execute the kitchen converge command to run out new apache recipe.
kitchen converge end
The kitchen converge fails because of apt-cache is not warmed up. For this, we can add the chef supermarket “apt” cookbook. You just have to add the dependency in our cookbooks metadata.rb file.
depends 'apt' end
Berkshelf is part of chekdk and it will automatically download the apt cookbook for us. Before that include the default apt recipe before the apache2 package code block. So out recipe would look like the following.
include_recipe 'apt::default' package "apache2" do action :install end end
Now run “kitchen converge” again. It will configure apache2 without any errors.
kitchen converge
Once the kitchen converge finishes execution, run “kitchen verify” command to run the integration tests.
kitchen verify
Now, you will see out integration test getting passed.
Hope this article gave a little idea about how to get started with chef test driven cookbook development.