Test Driven
Development!
for Puppet!
Puppet needs software development
Gareth Rushgrove
Who
(Who is this person?)
@garethr
UK Government
Digital Service
The problem
(This isn’t a rant, but…)
Who here is a
software developer?
Gareth Rushgrove
If you’re writing
Puppet code you’re a
software developer
Gareth Rushgrove
As a software
developer it’s your job
to learn software
engineering practices
Gareth Rushgrove
What is Test Driven
Development
(And why should you care)
A common practice in
software engineering
Gareth Rushgrove
Not just testing
Gareth Rushgrove
Encourages simple
designs and inspires
confidence
Gareth Rushgrove
First write an (initially
failing) automated
test case
Gareth Rushgrove
Then produce the
minimum amount of
code to pass that test
Gareth Rushgrove
And finally refactor
the new code to
acceptable standards
Gareth Rushgrove
Test Driven Design
Gareth Rushgrove
Gareth Rushgrove
Unit testing
with RSpec
and Guard
(Not puppet specific)
A unit is the smallest
testable part of an
application
Gareth Rushgrove
Testing puppet
requires a little Ruby
knowledge so we’ll
use Ruby examples
Gareth Rushgrove
class Person
def say(word)
end
end
Gareth Rushgrove
First lets write a test.
For this we use the
RSpec testing
framework
Gareth Rushgrove
require 'person'
!
describe Person, "#say" do
it "should say something" do
!
end
end
Gareth Rushgrove
require 'person'
!
describe Person, "#say" do
it "should say something" do
bob = Person.new
bob.say("hello").should 
eq("hello everyone")
end
end
Gareth Rushgrove
Now lets run our test.
It should fail
Gareth Rushgrove
rspec
Gareth Rushgrove
Failures:
1) Person#say should say something
Failure/Error: bob.say("hello").should
eq("hello everyone")
expected: "hello everyone"
got: nil
Finished in 0.00171 seconds
1 example, 1 failure
Gareth Rushgrove
Now lets write the
implementation
Gareth Rushgrove
class Person
def say(word)
word + " everyone"
end
end
Gareth Rushgrove
And run our test
again
Gareth Rushgrove
Person#say
should say something
!
Finished in 0.00199 seconds
1 example, 0 failures
Gareth Rushgrove
Why not have tests
automatically run
whenever you
change the code?
Gareth Rushgrove
That’s what Guard
does
Gareth Rushgrove
guard :rspec, cmd: 'bundle exec rspec' do
watch(%r{^spec/.+_spec.rb$})
watch(%r{^lib/.+.rb$}) { 'spec' }
end
Gareth Rushgrove
guard
Gareth Rushgrove
Lets see a quick
demo
Gareth Rushgrove
Why test puppet
code at all
(Testing declarative languages)
Modules increasingly
contain logic
Gareth Rushgrove
Modules increasingly
take arguments
Gareth Rushgrove
Modules increasingly
have interfaces with
other modules
Gareth Rushgrove
Modules increasingly
used in many
operating system and
version combinations
Gareth Rushgrove
Modules increasingly
used in many Ruby
and Puppet version
combinations
Gareth Rushgrove
Unit testing
puppet with
rspec-puppet
(Finally some puppet code)
Unit testing
for Puppet
A very simple puppet
class
Gareth Rushgrove
class sample {
}
Gareth Rushgrove
First write the test
Gareth Rushgrove
require 'spec_helper'
!
describe "sample" do
it { should create_file('/tmp/sample')}
end
Gareth Rushgrove
Then run the test
Gareth Rushgrove
sample
should contain File[/tmp/sample] (FAILED - 1)
!
Finished in 0.4584 seconds
1 example, 1 failure
Gareth Rushgrove
And then write the
(puppet) code to
make the test pass
Gareth Rushgrove
class sample {
file { "/tmp/sample":
ensure => present,
}
}
Gareth Rushgrove
sample
should contain File[/tmp/sample]
!
Finished in 0.3881 seconds
1 example, 0 failures
Gareth Rushgrove
Lets run the tests
automatically
whenever you
change anything
Gareth Rushgrove
guard :rspec, cmd: 'bundle exec rspec' do
watch(%r{^spec/.+_spec.rb$})
watch(%r{^manifests/.+.pp$}) { 'spec' }
end
Gareth Rushgrove
Lets see a quick
demo of that too
Gareth Rushgrove
You can also test
hosts, defines, facts,
functions, hieradata
Gareth Rushgrove
Syntax checking,
linting, oh my
(Creating a build process)
puppet-lint
Gareth Rushgrove
Puppet!
style guide
Available!
as a gem
puppet-lint --with-filename /etc/puppet/modules
foo/manifests/bar.pp: trailing whitespace found
on line 1 apache/manifests/server.pp: variable
not enclosed in {} on line 56
Gareth Rushgrove
puppet-syntax
Gareth Rushgrove
Validate Puppet
and ERB syntax
require 'puppet-syntax/tasks/puppet-syntax'
Gareth Rushgrove
rake syntax
---> syntax:manifests
---> syntax:templates
---> syntax:hiera:yaml
Gareth Rushgrove
What is Rake and
why do we use it
(Still no puppet)
Rake is a Ruby!
build tool
Gareth Rushgrove
It’s like Make but in
Ruby
Gareth Rushgrove
It’s very easy to
distribute Rake tasks
as Ruby gems
Gareth Rushgrove
rake
Gareth Rushgrove
rake <command>
Gareth Rushgrove
rake -T
Gareth Rushgrove
Lets make a
command to run lint,
syntax and spec
Gareth Rushgrove
task :test => [
:syntax,
:lint,
:spec,
]
Gareth Rushgrove
rake test
Gareth Rushgrove
Acceptance
testing with
beaker
(Living on the edge)
Acceptance
test against
real systems
Gareth Rushgrove
Test what actually
happens, not what is
meant to happen
Gareth Rushgrove
Build by
Gareth Rushgrove
Very new
Gareth Rushgrove
Test against different
operating systems
Gareth Rushgrove
HOSTS:
ubuntu-server-12042-x64:
roles:
- master
platform: ubuntu-server-12.04-amd64
box: ubuntu-server-12042-x64-vbox4210-nocm
box_url: http://puppet-vagrant-boxes.puppetlabs.com/u
hypervisor: vagrant
!
CONFIG:
log_level: verbose
type: foss
Gareth Rushgrove
HOSTS:
centos-64-x64:
roles:
- master
platform: el-6-x86_64
box : centos-64-x64-vbox4210-nocm
box_url : http://puppet-vagrant-boxes.puppetlabs.com/
hypervisor : vagrant
!
CONFIG:
log_level: verbose
type: foss
Gareth Rushgrove
Supports multiple
hypervisors
Gareth Rushgrove
Vagrant
hypervisor
VSphere
hypervisor
Helpers to install
puppet and modules
Gareth Rushgrove
install_puppet
Gareth Rushgrove
puppet('module', 'install', 'puppetlabs-stdlib')
Gareth Rushgrove
Test that Puppet runs
without errors
Gareth Rushgrove
context 'default parameters' do
it 'should work with no errors' do
pp = “class { 'sample': }”
!
expect(apply_manifest(pp).exit_code).to_not eq(1)
end
end
Gareth Rushgrove
Test runs are
idempotent
Gareth Rushgrove
context 'default parameters' do
it 'should work with no errors' do
pp = “class { 'sample': }”
!
expect(apply_manifest(pp).exit_code).to_not eq(1)
expect(apply_manifest(pp).exit_code).to eq(0)
end
end
Gareth Rushgrove
Test that the module
installs packages, run
services, etc.
Gareth Rushgrove
Gareth Rushgrove
describe package('nginx') do
it { should be_installed }
end
!
describe service('nginx') do
it { should be_enabled }
it { should be_running }
end
!
describe port(80) do
it { should be_listening}
end
Gareth Rushgrove
Other useful tools
(and what we’re still missing)
Fixtures,
matchers
Gareth Rushgrove
Nice continuous
integration
Test pull request
branches too
---
language: ruby
bundler_args: --without development
before_install: rm Gemfile.lock || true
rvm:
- 1.8.7
- 1.9.3
- 2.0.0
script: bundle exec rake test
env:
- PUPPET_VERSION="~> 2.7.0"
- PUPPET_VERSION="~> 3.1.0"
- PUPPET_VERSION="~> 3.2.0"
- PUPPET_VERSION="~> 3.3.0"
- PUPPET_VERSION="~> 3.4.0"
Gareth Rushgrove
Official!
ruby
support
matrix:
exclude:
- rvm: 2.0.0
env: PUPPET_VERSION="~> 2.7.0"
- rvm: 2.0.0
env: PUPPET_VERSION="~> 3.1.0"
- rvm: 1.9.3
env: PUPPET_VERSION="~> 2.7.0"
Gareth Rushgrove
Experimental code
coverage support in
rspec-puppet master
Gareth Rushgrove
at_exit { RSpec::Puppet::Coverage.report! }
Gareth Rushgrove
Total resources: 24
Touched resources: 8
Resource coverage: 33.33%
!
Untouched resources:
Class[Nginx]
File[preferences.d]
Anchor[apt::update]
Class[Apt::Params]
File[sources.list]
Exec[Required packages: 'debian-keyring debian-arch
Anchor[apt::source::nginx]
Class[Apt::Update]
File[configure-apt-proxy]
Apt::Key[Add key: 7BD9BF62 from Apt::Source nginx]
Anchor[apt::key/Add key: 7BD9BF62 from Apt::Source
Anchor[apt::key 7BD9BF62 present]
File[nginx.list]Gareth Rushgrove
A puppet module
skeleton with
everything working
out of the box
Gareth Rushgrove
puppet module
skeleton
puppet module generate sample
Gareth Rushgrove
A pretty complete
example
(The Docker module)
Gareth Rushgrove
Gareth Rushgrove
Featured on
the Forge
Gareth Rushgrove
50 pull request
and counting
Gareth Rushgrove
Contributing
guidelines
Gareth Rushgrove
Gareth Rushgrove
Currently has
121 tests
6 classes, 2 defines,
413 lines of puppet
code, 387 lines of
test code
Gareth Rushgrove
Take away
(If all you remember is…)
Infrastructure as
code
Gareth Rushgrove
The first test is the
hardest
Gareth Rushgrove
Politely demand tests
for contributions
Gareth Rushgrove
Test the interface not
the implementation
Gareth Rushgrove
Questions?
(And thanks for listening)

Test Driven Development with Puppet