Test Driven Development with Puppet

2,845 views

Published on

Presented at Puppet Camp London 2014 "Test Driven Development with Puppet" by Gareth Rushgrove, Government Digital Service

Test Driven Development with Puppet

  1. 1. Test Driven Development! for Puppet! Puppet needs software development Gareth Rushgrove
  2. 2. Who (Who is this person?)
  3. 3. @garethr
  4. 4. UK Government Digital Service
  5. 5. The problem (This isn’t a rant, but…)
  6. 6. Who here is a software developer? Gareth Rushgrove
  7. 7. If you’re writing Puppet code you’re a software developer Gareth Rushgrove
  8. 8. As a software developer it’s your job to learn software engineering practices Gareth Rushgrove
  9. 9. What is Test Driven Development (And why should you care)
  10. 10. A common practice in software engineering Gareth Rushgrove
  11. 11. Not just testing Gareth Rushgrove
  12. 12. Encourages simple designs and inspires confidence Gareth Rushgrove
  13. 13. First write an (initially failing) automated test case Gareth Rushgrove
  14. 14. Then produce the minimum amount of code to pass that test Gareth Rushgrove
  15. 15. And finally refactor the new code to acceptable standards Gareth Rushgrove
  16. 16. Test Driven Design Gareth Rushgrove
  17. 17. Gareth Rushgrove
  18. 18. Unit testing with RSpec and Guard (Not puppet specific)
  19. 19. A unit is the smallest testable part of an application Gareth Rushgrove
  20. 20. Testing puppet requires a little Ruby knowledge so we’ll use Ruby examples Gareth Rushgrove
  21. 21. class Person def say(word) end end Gareth Rushgrove
  22. 22. First lets write a test. For this we use the RSpec testing framework Gareth Rushgrove
  23. 23. require 'person' ! describe Person, "#say" do it "should say something" do ! end end Gareth Rushgrove
  24. 24. 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
  25. 25. Now lets run our test. It should fail Gareth Rushgrove
  26. 26. rspec Gareth Rushgrove
  27. 27. 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
  28. 28. Now lets write the implementation Gareth Rushgrove
  29. 29. class Person def say(word) word + " everyone" end end Gareth Rushgrove
  30. 30. And run our test again Gareth Rushgrove
  31. 31. Person#say should say something ! Finished in 0.00199 seconds 1 example, 0 failures Gareth Rushgrove
  32. 32. Why not have tests automatically run whenever you change the code? Gareth Rushgrove
  33. 33. That’s what Guard does Gareth Rushgrove
  34. 34. guard :rspec, cmd: 'bundle exec rspec' do watch(%r{^spec/.+_spec.rb$}) watch(%r{^lib/.+.rb$}) { 'spec' } end Gareth Rushgrove
  35. 35. guard Gareth Rushgrove
  36. 36. Lets see a quick demo Gareth Rushgrove
  37. 37. Why test puppet code at all (Testing declarative languages)
  38. 38. Modules increasingly contain logic Gareth Rushgrove
  39. 39. Modules increasingly take arguments Gareth Rushgrove
  40. 40. Modules increasingly have interfaces with other modules Gareth Rushgrove
  41. 41. Modules increasingly used in many operating system and version combinations Gareth Rushgrove
  42. 42. Modules increasingly used in many Ruby and Puppet version combinations Gareth Rushgrove
  43. 43. Unit testing puppet with rspec-puppet (Finally some puppet code)
  44. 44. Unit testing for Puppet
  45. 45. A very simple puppet class Gareth Rushgrove
  46. 46. class sample { } Gareth Rushgrove
  47. 47. First write the test Gareth Rushgrove
  48. 48. require 'spec_helper' ! describe "sample" do it { should create_file('/tmp/sample')} end Gareth Rushgrove
  49. 49. Then run the test Gareth Rushgrove
  50. 50. sample should contain File[/tmp/sample] (FAILED - 1) ! Finished in 0.4584 seconds 1 example, 1 failure Gareth Rushgrove
  51. 51. And then write the (puppet) code to make the test pass Gareth Rushgrove
  52. 52. class sample { file { "/tmp/sample": ensure => present, } } Gareth Rushgrove
  53. 53. sample should contain File[/tmp/sample] ! Finished in 0.3881 seconds 1 example, 0 failures Gareth Rushgrove
  54. 54. Lets run the tests automatically whenever you change anything Gareth Rushgrove
  55. 55. guard :rspec, cmd: 'bundle exec rspec' do watch(%r{^spec/.+_spec.rb$}) watch(%r{^manifests/.+.pp$}) { 'spec' } end Gareth Rushgrove
  56. 56. Lets see a quick demo of that too Gareth Rushgrove
  57. 57. You can also test hosts, defines, facts, functions, hieradata Gareth Rushgrove
  58. 58. Syntax checking, linting, oh my (Creating a build process)
  59. 59. puppet-lint Gareth Rushgrove
  60. 60. Puppet! style guide
  61. 61. Available! as a gem
  62. 62. 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
  63. 63. puppet-syntax Gareth Rushgrove
  64. 64. Validate Puppet and ERB syntax
  65. 65. require 'puppet-syntax/tasks/puppet-syntax' Gareth Rushgrove
  66. 66. rake syntax ---> syntax:manifests ---> syntax:templates ---> syntax:hiera:yaml Gareth Rushgrove
  67. 67. What is Rake and why do we use it (Still no puppet)
  68. 68. Rake is a Ruby! build tool Gareth Rushgrove
  69. 69. It’s like Make but in Ruby Gareth Rushgrove
  70. 70. It’s very easy to distribute Rake tasks as Ruby gems Gareth Rushgrove
  71. 71. rake Gareth Rushgrove
  72. 72. rake <command> Gareth Rushgrove
  73. 73. rake -T Gareth Rushgrove
  74. 74. Lets make a command to run lint, syntax and spec Gareth Rushgrove
  75. 75. task :test => [ :syntax, :lint, :spec, ] Gareth Rushgrove
  76. 76. rake test Gareth Rushgrove
  77. 77. Acceptance testing with beaker (Living on the edge)
  78. 78. Acceptance test against real systems Gareth Rushgrove
  79. 79. Test what actually happens, not what is meant to happen Gareth Rushgrove
  80. 80. Build by Gareth Rushgrove
  81. 81. Very new Gareth Rushgrove
  82. 82. Test against different operating systems Gareth Rushgrove
  83. 83. 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
  84. 84. 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
  85. 85. Supports multiple hypervisors Gareth Rushgrove
  86. 86. Vagrant hypervisor
  87. 87. VSphere hypervisor
  88. 88. Helpers to install puppet and modules Gareth Rushgrove
  89. 89. install_puppet Gareth Rushgrove
  90. 90. puppet('module', 'install', 'puppetlabs-stdlib') Gareth Rushgrove
  91. 91. Test that Puppet runs without errors Gareth Rushgrove
  92. 92. 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
  93. 93. Test runs are idempotent Gareth Rushgrove
  94. 94. 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
  95. 95. Test that the module installs packages, run services, etc. Gareth Rushgrove
  96. 96. Gareth Rushgrove
  97. 97. 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
  98. 98. Other useful tools (and what we’re still missing)
  99. 99. Fixtures, matchers
  100. 100. Gareth Rushgrove
  101. 101. Nice continuous integration
  102. 102. Test pull request branches too
  103. 103. --- 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
  104. 104. Official! ruby support
  105. 105. 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
  106. 106. Experimental code coverage support in rspec-puppet master Gareth Rushgrove
  107. 107. at_exit { RSpec::Puppet::Coverage.report! } Gareth Rushgrove
  108. 108. 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
  109. 109. A puppet module skeleton with everything working out of the box Gareth Rushgrove
  110. 110. puppet module skeleton
  111. 111. puppet module generate sample Gareth Rushgrove
  112. 112. A pretty complete example (The Docker module)
  113. 113. Gareth Rushgrove
  114. 114. Gareth Rushgrove Featured on the Forge
  115. 115. Gareth Rushgrove 50 pull request and counting
  116. 116. Gareth Rushgrove Contributing guidelines
  117. 117. Gareth Rushgrove
  118. 118. Gareth Rushgrove Currently has 121 tests
  119. 119. 6 classes, 2 defines, 413 lines of puppet code, 387 lines of test code Gareth Rushgrove
  120. 120. Take away (If all you remember is…)
  121. 121. Infrastructure as code Gareth Rushgrove
  122. 122. The first test is the hardest Gareth Rushgrove
  123. 123. Politely demand tests for contributions Gareth Rushgrove
  124. 124. Test the interface not the implementation Gareth Rushgrove
  125. 125. Questions? (And thanks for listening)

×