Using Test Kitchen
for testing Chef cookbooks
Installation
$ brew cask install chefdk
==> Satisfying dependencies
==> Downloading https://packages.chef.io/files/stable/chefdk/2.1.11/mac_os_x/
10.12/chefdk-2.1.11-1.dmg
######################################################################## 100.0%
==> Verifying checksum for Cask chefdk
==> Installing Cask chefdk
==> Running installer for chefdk; your password may be necessary.
==> Package installers may write to any location; options such as --appdir are
ignored.
==> installer: Package name is Chef Development Kit
==> installer: Installing at base path /
==> installer: The install was successful.
🍺 chefdk was successfully installed!
$ kitchen version
Test Kitchen version 1.17.0
$
You’ll also need to have Docker installed and working.
Basic Test-Kitchen
commands
Basic commands:
kitchen list
• kitchen list: browse available VMs and their status:

$ kitchen list
Instance Driver Provisioner Verifier Transport Last Action Last Error
default-ubuntu-1204-11 Docker ChefZero Inspec Ssh <Created> <None>
default-ubuntu-1204-latest Docker ChefZero Inspec Ssh <Not Created> <None>
default-ubuntu-1604-11 Docker ChefZero Inspec Ssh <Not Created> <None>
default-ubuntu-1604-latest Docker ChefZero Inspec Ssh <Not Created> <None>
Basic commands:
kitchen test
• kitchen test: run the tests

$ time kitchen test default-ubuntu-1204-11
-----> Starting Kitchen (v1.17.0)
-----> Cleaning up any prior instances of <default-ubuntu-1204-11>
-----> Creating <default-ubuntu-1204-11>…
-----> Converging <default-ubuntu-1204-11>…
-----> Installing Chef Omnibus (11.18.6)
Starting Chef Client, version 11.18.6
Converging 1 resources
Recipe: jfrog::default
* remote_file[jfrog-cli] action create
- create new file /usr/local/bin/jfrog-cli
Chef Client finished, 1/1 resources updated in 13.388088718 seconds
-----> Verifying <default-ubuntu-1204-11>...
Loaded tests from test/smoke/default
Command jfrog-cli
✔ should exist
Test Summary: 1 successful, 0 failures, 0 skipped
-----> Destroying <default-ubuntu-1204-11>...
-----> Kitchen is finished. (1m13.38s)
real 1m14.228s
Testing phases
• kitchen create: Start an instance (or several)

• kitchen converge: Run chef in the instance

• kitchen verify: Run automated tests

• kitchen destroy: Destroy one or more instances

• kitchen test: destroy -> create -> converge -> verify -
> destroy

• kitchen login: ssh into the instance
My workflow
1. kitchen create default-ubuntu-1204-11
2. Work on cookbook/tests 

3. kitchen converge default-ubuntu-1204-11 #
make sure there are no errors
4. kitchen verify default-ubuntu-1204-11
5. repeat 2-4

6. Run tests for all OSes and Chef versions:

kitchen test # with no params
.kitchen.yml
.kitchen.yml sections
---
driver:
name: docker
use_sudo: false
use_cache: true
provision_command:
- apt-get update
- apt-get -y dist-upgrade
- apt-get install -y iputils-ping net-tools telnet vim
dnsutils less
run_command: sh -c 'apt-get update && exec /sbin/init'
disable_upstart: false
<% if ENV['KITCHEN_PRIVILEGED'] %>
## enable this if you need extra capabilities like mounting
volumes or running systemd
## requires Docker privileged mode enabled or crashes the run
otherwise
privileged: true
<% end %>
.kitchen.yml sections
platforms:
- name: ubuntu-12.04-11
driver:
image: ubuntu-upstart:12.04 # upstart doesn't work in stock ubuntu image
provisioner:
require_chef_omnibus: 11.18.6
- name: ubuntu-12.04-latest
driver:
image: ubuntu-upstart:12.04 # upstart doesn't work in stock ubuntu image
provisioner:
require_chef_omnibus: latest
<% if ENV['KITCHEN_PRIVILEGED'] %>
## systemd doesn't work in non-privileged mode
- name: ubuntu-16.04-11
driver:
image: ubuntu:16.04
provisioner:
require_chef_omnibus: 11.18.6
- name: ubuntu-16.04-latest
driver:
image: ubuntu:16.04
provisioner:
require_chef_omnibus: latest
<% end %>
.kitchen.yml sections
suites:
- name: default
run_list:
# - recipe[configure_kitchen_vm] # uncomment if you need setup
for tests
- recipe[jfrog::default]
verifier:
inspec_tests:
- test/smoke/default
attributes:
search_roles:
distfiles: reposerver
local_infrastructure_env: lienv
Configuration around
cookbooks
• Attributes: set up in .kitchen.yml

• Nodes (for search): test/integration/nodes

• Data bags: test/integration/data_bags

• Environments: test/integration/environments
Example node
test/integration/nodes/reposerver.json:

{
"name": "reposerver-01",
"chef_environment": “lienv",
"run_list": [
"role[reposerver]"
],
"normal": {
"distfiles": {
"path": "distfiles"
},
"tags": [
]
},
"automatic": {
"ipaddress": “192.168.40.40"
}
}
Dependencies/setup
Berksfile:

source 'https://supermarket.chef.io'
metadata
cookbook 'cheflib', git: 'git@bitbucket.org:myorg/chef-
cheflib.git'
cookbook 'jfrog', git: 'git@bitbucket.org:myorg/chef-jfrog.git'
cookbook 'unicorn-ng', git: 'git@bitbucket.org:myorg/chef-
unicorn-ng.git', branch: 'dev'
### Uncomment and edit if you need special setup for running
tests
group :integration do
cookbook 'configure_kitchen_vm', path: 'test/fixtures/
cookbooks/configure_kitchen_vm'
end
InSpec tests
require_relative '../spec_helper'
describe command('/opt/app/embedded/bin/ruby') do
it { should exist }
end
describe command('/opt/app/embedded/bin/ruby --version') do
its(:stdout) { should match /2.3.1/ }
end
describe service('app_service') do
it { should be_enabled }
it { should be_running }
end
describe processes('unicorn') do
its('list.length') { should eq 11 } # 10 worker threads + master
end
So much boilerplate?
• git clone https://bitbucket.org/myorg/chef-generator

use stock generator or copy from ChefDK for own modifications

• add to your knife.rb/config.rb:

chefdk.generator_cookbook = ‘/path/to/chef-generator’

• Generate all the boilerplate:

$ chef generate cookbook mynewcookbook
Your cookbook is ready. Type `cd my_new_app` to enter it.
$ cd mynewcookbook; ls -1
Berksfile
CHANGELOG.md
Gemfile
README.md
chefignore
cleanup.sh
metadata.rb
recipes
run_lints.sh
run_tests.sh
spec
test
Summarize
• Have ChefDK, Docker and generator cookbook installed and configured

• chef generate cookbook mynewcookbook
• Create tests and code

• Update Berksfile if you need dependencies for other cookbooks

• Add attributes to .kitchen.yml, nodes and envs to test/
integration/* as needed

• kitchen create/converge/verify default-ubuntu-1204-11
• At the very end run kitchen tests without params to confirm everything
works on all platforms

Using Test Kitchen for testing Chef cookbooks

  • 1.
    Using Test Kitchen fortesting Chef cookbooks
  • 2.
    Installation $ brew caskinstall chefdk ==> Satisfying dependencies ==> Downloading https://packages.chef.io/files/stable/chefdk/2.1.11/mac_os_x/ 10.12/chefdk-2.1.11-1.dmg ######################################################################## 100.0% ==> Verifying checksum for Cask chefdk ==> Installing Cask chefdk ==> Running installer for chefdk; your password may be necessary. ==> Package installers may write to any location; options such as --appdir are ignored. ==> installer: Package name is Chef Development Kit ==> installer: Installing at base path / ==> installer: The install was successful. 🍺 chefdk was successfully installed! $ kitchen version Test Kitchen version 1.17.0 $ You’ll also need to have Docker installed and working.
  • 3.
  • 4.
    Basic commands: kitchen list •kitchen list: browse available VMs and their status: $ kitchen list Instance Driver Provisioner Verifier Transport Last Action Last Error default-ubuntu-1204-11 Docker ChefZero Inspec Ssh <Created> <None> default-ubuntu-1204-latest Docker ChefZero Inspec Ssh <Not Created> <None> default-ubuntu-1604-11 Docker ChefZero Inspec Ssh <Not Created> <None> default-ubuntu-1604-latest Docker ChefZero Inspec Ssh <Not Created> <None>
  • 5.
    Basic commands: kitchen test •kitchen test: run the tests $ time kitchen test default-ubuntu-1204-11 -----> Starting Kitchen (v1.17.0) -----> Cleaning up any prior instances of <default-ubuntu-1204-11> -----> Creating <default-ubuntu-1204-11>… -----> Converging <default-ubuntu-1204-11>… -----> Installing Chef Omnibus (11.18.6) Starting Chef Client, version 11.18.6 Converging 1 resources Recipe: jfrog::default * remote_file[jfrog-cli] action create - create new file /usr/local/bin/jfrog-cli Chef Client finished, 1/1 resources updated in 13.388088718 seconds -----> Verifying <default-ubuntu-1204-11>... Loaded tests from test/smoke/default Command jfrog-cli ✔ should exist Test Summary: 1 successful, 0 failures, 0 skipped -----> Destroying <default-ubuntu-1204-11>... -----> Kitchen is finished. (1m13.38s) real 1m14.228s
  • 6.
    Testing phases • kitchencreate: Start an instance (or several) • kitchen converge: Run chef in the instance • kitchen verify: Run automated tests • kitchen destroy: Destroy one or more instances • kitchen test: destroy -> create -> converge -> verify - > destroy • kitchen login: ssh into the instance
  • 7.
    My workflow 1. kitchencreate default-ubuntu-1204-11 2. Work on cookbook/tests 3. kitchen converge default-ubuntu-1204-11 # make sure there are no errors 4. kitchen verify default-ubuntu-1204-11 5. repeat 2-4 6. Run tests for all OSes and Chef versions:
 kitchen test # with no params
  • 8.
  • 9.
    .kitchen.yml sections --- driver: name: docker use_sudo:false use_cache: true provision_command: - apt-get update - apt-get -y dist-upgrade - apt-get install -y iputils-ping net-tools telnet vim dnsutils less run_command: sh -c 'apt-get update && exec /sbin/init' disable_upstart: false <% if ENV['KITCHEN_PRIVILEGED'] %> ## enable this if you need extra capabilities like mounting volumes or running systemd ## requires Docker privileged mode enabled or crashes the run otherwise privileged: true <% end %>
  • 10.
    .kitchen.yml sections platforms: - name:ubuntu-12.04-11 driver: image: ubuntu-upstart:12.04 # upstart doesn't work in stock ubuntu image provisioner: require_chef_omnibus: 11.18.6 - name: ubuntu-12.04-latest driver: image: ubuntu-upstart:12.04 # upstart doesn't work in stock ubuntu image provisioner: require_chef_omnibus: latest <% if ENV['KITCHEN_PRIVILEGED'] %> ## systemd doesn't work in non-privileged mode - name: ubuntu-16.04-11 driver: image: ubuntu:16.04 provisioner: require_chef_omnibus: 11.18.6 - name: ubuntu-16.04-latest driver: image: ubuntu:16.04 provisioner: require_chef_omnibus: latest <% end %>
  • 11.
    .kitchen.yml sections suites: - name:default run_list: # - recipe[configure_kitchen_vm] # uncomment if you need setup for tests - recipe[jfrog::default] verifier: inspec_tests: - test/smoke/default attributes: search_roles: distfiles: reposerver local_infrastructure_env: lienv
  • 12.
    Configuration around cookbooks • Attributes:set up in .kitchen.yml • Nodes (for search): test/integration/nodes • Data bags: test/integration/data_bags • Environments: test/integration/environments
  • 13.
    Example node test/integration/nodes/reposerver.json: { "name": "reposerver-01", "chef_environment":“lienv", "run_list": [ "role[reposerver]" ], "normal": { "distfiles": { "path": "distfiles" }, "tags": [ ] }, "automatic": { "ipaddress": “192.168.40.40" } }
  • 14.
    Dependencies/setup Berksfile: source 'https://supermarket.chef.io' metadata cookbook 'cheflib',git: 'git@bitbucket.org:myorg/chef- cheflib.git' cookbook 'jfrog', git: 'git@bitbucket.org:myorg/chef-jfrog.git' cookbook 'unicorn-ng', git: 'git@bitbucket.org:myorg/chef- unicorn-ng.git', branch: 'dev' ### Uncomment and edit if you need special setup for running tests group :integration do cookbook 'configure_kitchen_vm', path: 'test/fixtures/ cookbooks/configure_kitchen_vm' end
  • 15.
    InSpec tests require_relative '../spec_helper' describecommand('/opt/app/embedded/bin/ruby') do it { should exist } end describe command('/opt/app/embedded/bin/ruby --version') do its(:stdout) { should match /2.3.1/ } end describe service('app_service') do it { should be_enabled } it { should be_running } end describe processes('unicorn') do its('list.length') { should eq 11 } # 10 worker threads + master end
  • 16.
    So much boilerplate? •git clone https://bitbucket.org/myorg/chef-generator
 use stock generator or copy from ChefDK for own modifications • add to your knife.rb/config.rb:
 chefdk.generator_cookbook = ‘/path/to/chef-generator’ • Generate all the boilerplate:
 $ chef generate cookbook mynewcookbook Your cookbook is ready. Type `cd my_new_app` to enter it. $ cd mynewcookbook; ls -1 Berksfile CHANGELOG.md Gemfile README.md chefignore cleanup.sh metadata.rb recipes run_lints.sh run_tests.sh spec test
  • 17.
    Summarize • Have ChefDK,Docker and generator cookbook installed and configured • chef generate cookbook mynewcookbook • Create tests and code • Update Berksfile if you need dependencies for other cookbooks • Add attributes to .kitchen.yml, nodes and envs to test/ integration/* as needed • kitchen create/converge/verify default-ubuntu-1204-11 • At the very end run kitchen tests without params to confirm everything works on all platforms