Written By: Christopher Webber (@cwebber)
So, you read the great article by Paul Czarkowski (@pczarkowski) on December 11th, The Lazy SysAdmin's Guide to Test Driven Chef Cookbooks, but felt left out because you run Puppet... Well this article is for you.
Getting the Environment Setup
The first hurdle to get over is the version of Ruby and where it is coming from. My personal preference is to use something like RVM or rbenv. But the only real thing I recommend is making sure you are using a version of ruby that is the same as what you have in production. I have been bitten by things that work in 1.8.7, the version of Ruby that Puppet uses in production in my environment, but didn't work in 1.9.3, the version I was testing with on my workstation.
The Gems
You will need/want the following gems:
- puppet (Once again, use the version you are running in production)
- rspec-puppet
- puppet-lint
- puppetlabs_spec_helper
Once you have installed the gems, you should be ready to move on to the next step.
Generating A New Module
For this we are going to use the handy puppet module
command to generate our starting module. We will be working with a module called cwebber-example
.
$ puppet module generate cwebber-example
Notice: Generating module at /Users/cwebber/cwebber-example
cwebber-example
cwebber-example/tests
cwebber-example/tests/init.pp
cwebber-example/spec
cwebber-example/spec/spec_helper.rb
cwebber-example/README
cwebber-example/Modulefile
cwebber-example/manifests
cwebber-example/manifests/init.pp
Getting some testing boilerplate in place
Before we start building the boilerplate, it is useful to create the templates. Additionally, I think the boilerplate spec_helper.rb
that rspec-puppet creates is a better starting point, so I run rm -rf spec
to allow the rspec-puppet files to be used. To get the boilerplate in place run:
$ rspec-puppet-init
+ spec/
+ spec/classes/
+ spec/defines/
+ spec/functions/
+ spec/hosts/
+ spec/fixtures/
+ spec/fixtures/manifests/
+ spec/fixtures/modules/
+ spec/fixtures/modules/example/
+ spec/fixtures/manifests/site.pp
+ spec/fixtures/modules/example/manifests
+ spec/fixtures/modules/example/templates
+ spec/spec_helper.rb
+ Rakefile
Fixing the rake tasks
Finally, I make a slight change to the Rakefile
so that some a few additional tasks are supported and to enable a few advanced features we will touch on a little later. Update your Rakefile
to look like the following:
require 'rake'
require 'rspec/core/rake_task'
require 'puppetlabs_spec_helper/rake_tasks'
Writing the first test
Given the popularity of TDD, I am going to show the example of writing the test first. While this makes sense for this example, please don't feel bad if you find yourself writing the code first and writing tests to cover that code later.
To keep things simple, we are going to use puppet to create a file called /foo
with the contents bar
. While this is a little arbitrary, we can get a feel for what it might look like to test a config file. We are going to put this file resource in the init.pp
for the module we created.
Creating the test involves adding the following contents to the file spec/classes/example_spec.rb
.
require 'spec_helper'
describe "example" do
it do
should contain_file('/foo').with({
'ensure' => 'present',
'content' => %r{^bar}
})
end
Running this test should fail as the resource has not been added to the class yet. To run the test, we run rake spec
to kick it off.
$ rake spec
/usr/bin/ruby -S rspec spec/classes/example_spec.rb --color
F
Failures:
1) example should contain File[/foo] with ensure => "present" and content matching /^bar/
Failure/Error: })
expected that the catalogue would contain File[/foo]
# ./spec/classes/example_spec.rb:9
Finished in 0.08232 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/classes/example_spec.rb:5 # example should contain File[/foo] with ensure => "present" and content matching /^bar/
/usr/bin/ruby -S rspec spec/classes/example_spec.rb --color failed
Now that there is a failing test, time to create the puppet code to go with it.
The corresponding puppet code
The puppet code that goes with this test is pretty straight forward. In manifests/init.pp
we add the following inside the example
class.
file {'/foo':
ensure => present,
content => 'bar'
}
Now that we have code in place, lets test to make sure it passes our test by running rake spec
again.
$ rake spec
/usr/bin/ruby -S rspec spec/classes/example_spec.rb --color
.
Finished in 0.07837 seconds
1 example, 0 failures
Useful things to know about
~/.rspec
One of the things I never cared for was the lack of information when you had passing tests. If you add --format documentation
to ~/.rspec
you get output that looks like this:
/usr/bin/ruby -S rspec spec/classes/example_spec.rb --color
example
should contain File[/foo] with ensure => "present" and content matching /^bar/
Finished in 0.09379 seconds
1 example, 0 failures
rake lint
One of the additional tasks that gets added when we change the Rakefile is the lint
task. This task runs puppet-lint across all of the puppet files, verifying that they pass a certain coding standard. To get more info, please visit http://puppet-lint.com.
.fixtures.yml
As soon as you start to get into more complex modules, you see a dependency on other modules. The .fixtures.yml
file is there to help make sure that the appropriate puppet modules are checked out for testing use. See https://github.com/puppetlabs/puppetlabs_spec_helper#using-fixtures for more details.
The snippet for spec/classes/example_spec.rb is incomplete. There should be an extra "end" at the bottom.
ReplyDelete