Post on 10-May-2015
description
Test Driven Development!for Puppet!Puppet needs software developmentGareth 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/ubuntu-server-12042-x64-vbox4210-nocm.box 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/centos-64-x64-vbox4210-nocm.box 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-archive-keyring' for nginx] 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 nginx] Anchor[apt::key 7BD9BF62 present] File[nginx.list] Exec[apt_update]
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)