Sunteți pe pagina 1din 129

Test Driven Development ! for Puppet !

Test Driven Development ! for Puppet ! Puppet needs software development Gareth Rushgrove

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

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

Build by Gareth Rushgrove

Very new

Gareth Rushgrove

Test against different operating systems

Gareth Rushgrove

HOSTS:

ubuntu-server-12042-x64:

roles:

!

CONFIG:

log_level: verbose type: foss

- 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

Gareth Rushgrove

HOSTS:

centos-64-x64:

roles:

!

CONFIG:

log_level: verbose type: foss

- master platform: el-6-x86_64 box : centos-64-x64-vbox4210-nocm box_url : http://puppet-vagrant-boxes.puppetlabs.com/ hypervisor : vagrant

Gareth Rushgrove

Supports multiple hypervisors

Gareth Rushgrove

Vagrant hypervisor

Vagrant

hypervisor

VSphere 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

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

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:

Touched resources: 8 Resource coverage: 33.33%

24

Untouched resources:

Class[Nginx] File[preferences.d] Anchor[apt::update] Class[Apt::Params] File[sources.list] Exec[Required packages: 'debian-keyring debian-arc 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

Featured on the Forge

Gareth Rushgrove

50 pull request and counting

Gareth Rushgrove

Contributing

guidelines

Gareth Rushgrove

Gareth Rushgrove
Gareth Rushgrove

Gareth Rushgrove

Currently has 121 tests

Gareth Rushgrove

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)