Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Test Driven Development for Puppet

Test Driven Development for Puppet

Puppet Camp London presentation introducing the tools and techniques used for testing Puppet code. Also introduces Test Driven Development, what it is and why it's useful.


Gareth Rushgrove

April 04, 2014

More Decks by Gareth Rushgrove

Other Decks in Programming


  1. Test Driven Development! for Puppet! Puppet needs software development Gareth

  2. Who (Who is this person?)

  3. @garethr

  4. UK Government Digital Service

  5. None
  6. None
  7. None
  8. The problem (This isn’t a rant, but…)

  9. Who here is a software developer? Gareth Rushgrove

  10. If you’re writing Puppet code you’re a software developer Gareth

  11. As a software developer it’s your job to learn software

    engineering practices Gareth Rushgrove
  12. What is Test Driven Development (And why should you care)

  13. A common practice in software engineering Gareth Rushgrove

  14. Not just testing Gareth Rushgrove

  15. Encourages simple designs and inspires confidence Gareth Rushgrove

  16. First write an (initially failing) automated test case Gareth Rushgrove

  17. Then produce the minimum amount of code to pass that

    test Gareth Rushgrove
  18. And finally refactor the new code to acceptable standards Gareth

  19. Test Driven Design Gareth Rushgrove

  20. Gareth Rushgrove

  21. Unit testing with RSpec and Guard (Not puppet specific)

  22. A unit is the smallest testable part of an application

    Gareth Rushgrove
  23. Testing puppet requires a little Ruby knowledge so we’ll use

    Ruby examples Gareth Rushgrove
  24. class Person def say(word) end end Gareth Rushgrove

  25. First lets write a test. For this we use the

    RSpec testing framework Gareth Rushgrove
  26. require 'person' ! describe Person, "#say" do it "should say

    something" do ! end end Gareth Rushgrove
  27. 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
  28. Now lets run our test. It should fail Gareth Rushgrove

  29. rspec Gareth Rushgrove

  30. 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
  31. Now lets write the implementation Gareth Rushgrove

  32. class Person def say(word) word + " everyone" end end

    Gareth Rushgrove
  33. And run our test again Gareth Rushgrove

  34. Person#say should say something ! Finished in 0.00199 seconds 1

    example, 0 failures Gareth Rushgrove
  35. Why not have tests automatically run whenever you change the

    code? Gareth Rushgrove
  36. That’s what Guard does Gareth Rushgrove

  37. guard :rspec, cmd: 'bundle exec rspec' do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/.+\.rb$}) {

    'spec' } end Gareth Rushgrove
  38. guard Gareth Rushgrove

  39. Lets see a quick demo Gareth Rushgrove

  40. Why test puppet code at all (Testing declarative languages)

  41. Modules increasingly contain logic Gareth Rushgrove

  42. Modules increasingly take arguments Gareth Rushgrove

  43. Modules increasingly have interfaces with other modules Gareth Rushgrove

  44. Modules increasingly used in many operating system and version combinations

    Gareth Rushgrove
  45. Modules increasingly used in many Ruby and Puppet version combinations

    Gareth Rushgrove
  46. Unit testing puppet with rspec-puppet (Finally some puppet code)

  47. Unit testing for Puppet

  48. A very simple puppet class Gareth Rushgrove

  49. class sample { } Gareth Rushgrove

  50. First write the test Gareth Rushgrove

  51. require 'spec_helper' ! describe "sample" do it { should create_file('/tmp/sample')}

    end Gareth Rushgrove
  52. Then run the test Gareth Rushgrove

  53. sample should contain File[/tmp/sample] (FAILED - 1) ! Finished in

    0.4584 seconds 1 example, 1 failure Gareth Rushgrove
  54. And then write the (puppet) code to make the test

    pass Gareth Rushgrove
  55. class sample { file { "/tmp/sample": ensure => present, }

    } Gareth Rushgrove
  56. sample should contain File[/tmp/sample] ! Finished in 0.3881 seconds 1

    example, 0 failures Gareth Rushgrove
  57. Lets run the tests automatically whenever you change anything Gareth

  58. guard :rspec, cmd: 'bundle exec rspec' do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^manifests/.+\.pp$}) {

    'spec' } end Gareth Rushgrove
  59. Lets see a quick demo of that too Gareth Rushgrove

  60. You can also test hosts, defines, facts, functions, hieradata Gareth

  61. Syntax checking, linting, oh my (Creating a build process)

  62. puppet-lint Gareth Rushgrove

  63. Puppet! style guide

  64. Available! as a gem

  65. 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
  66. puppet-syntax Gareth Rushgrove

  67. Validate Puppet and ERB syntax

  68. require 'puppet-syntax/tasks/puppet-syntax' Gareth Rushgrove

  69. rake syntax ---> syntax:manifests ---> syntax:templates ---> syntax:hiera:yaml Gareth Rushgrove

  70. What is Rake and why do we use it (Still

    no puppet)
  71. Rake is a Ruby! build tool Gareth Rushgrove

  72. It’s like Make but in Ruby Gareth Rushgrove

  73. It’s very easy to distribute Rake tasks as Ruby gems

    Gareth Rushgrove
  74. rake Gareth Rushgrove

  75. rake <command> Gareth Rushgrove

  76. rake -T Gareth Rushgrove

  77. Lets make a command to run lint, syntax and spec

    Gareth Rushgrove
  78. task :test => [ :syntax, :lint, :spec, ] Gareth Rushgrove

  79. rake test Gareth Rushgrove

  80. Acceptance testing with beaker (Living on the edge)

  81. Acceptance test against real systems Gareth Rushgrove

  82. Test what actually happens, not what is meant to happen

    Gareth Rushgrove
  83. Build by Gareth Rushgrove

  84. Very new Gareth Rushgrove

  85. Test against different operating systems Gareth Rushgrove

  86. 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
  87. 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
  88. Supports multiple hypervisors Gareth Rushgrove

  89. Vagrant hypervisor

  90. VSphere hypervisor

  91. Helpers to install puppet and modules Gareth Rushgrove

  92. install_puppet Gareth Rushgrove

  93. puppet('module', 'install', 'puppetlabs-stdlib') Gareth Rushgrove

  94. Test that Puppet runs without errors Gareth Rushgrove

  95. 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
  96. Test runs are idempotent Gareth Rushgrove

  97. 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
  98. Test that the module installs packages, run services, etc. Gareth

  99. Gareth Rushgrove

  100. 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
  101. Other useful tools (and what we’re still missing)

  102. Fixtures, matchers

  103. Gareth Rushgrove

  104. Nice continuous integration

  105. None
  106. Test pull request branches too

  107. --- 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
  108. Official! ruby support

  109. 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
  110. Experimental code coverage support in rspec-puppet master Gareth Rushgrove

  111. at_exit { RSpec::Puppet::Coverage.report! } Gareth Rushgrove

  112. 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
  113. A puppet module skeleton with everything working out of the

    box Gareth Rushgrove
  114. puppet module skeleton

  115. puppet module generate sample Gareth Rushgrove

  116. A pretty complete example (The Docker module)

  117. Gareth Rushgrove

  118. Gareth Rushgrove Featured on the Forge

  119. Gareth Rushgrove 50 pull request and counting

  120. Gareth Rushgrove Contributing guidelines

  121. Gareth Rushgrove

  122. Gareth Rushgrove Currently has 121 tests

  123. 6 classes, 2 defines, 413 lines of puppet code, 387

    lines of test code Gareth Rushgrove
  124. Take away (If all you remember is…)

  125. Infrastructure as code Gareth Rushgrove

  126. The first test is the hardest Gareth Rushgrove

  127. Politely demand tests for contributions Gareth Rushgrove

  128. Test the interface not the implementation Gareth Rushgrove

  129. Questions? (And thanks for listening)