Puppet Camp Paris 2014: Test Driven Development

1,298 views

Published on

"Test Driven Development" given by Open Source Consultant Johan De Wit at Puppet Camp Paris 2014

Puppet Camp Paris 2014: Test Driven Development

  1. 1. Test Driven Development and Puppet Puppet Camp Paris April 8, 2014 Johan De Wit (johan@open-future.be)
  2. 2. Whoami ● Open Source Consultant @ open-future ● Organiser Belgian Puppet User Group ● A Sys-Admin ● A very poor developer (but working on it) ● Love riding : ● Bikes ● horses
  3. 3. Do you test your modules ? I did !! ● 1: write a module ● 2: puppet parser validate ? ● 3: puppet-lint ? ● 4: puppet apply tests/init.pp (smoke) ● 5: puppet agent -t --noop – You use vagrant – right ??
  4. 4. Start over again ● Me => devops ● Should be DEVops – Yes, we write code to manage our infrastructure. – Learn from Developers ● UNIT TESTING ● INTEGRATION TESTING ● ACCEPTANCE TESTING ● .....
  5. 5. Testing is great ● Confidence changing things ● Discover breaking things before deploy ● Test against # puppet & # ruby versions ● Test many os'es without deploying them ● Test early - fast feedback ● Prevent regression of old problems
  6. 6. First thing first ● Unit testing – Rspec-puppet – Start with testing, then coding ● It is the beginning of .. – Integration testing (beaker) – Travis – Jenkins – ....
  7. 7. Think colors 4 Test Puppet Code
  8. 8. GoTest Driven Development Source http://centricconsulting.com/agile-test-driven-development/
  9. 9. Benefits of TDD ● Test case from the beginning ● Better code coverage ● Tests are maintained during life cycle ● Focus on the needed functionality, step by step ● Encourage simple design (avoid over engineering) ● First step in test automation (unit testing)
  10. 10. TDD does not ● Replace integration testing ● Replace compliance testing ● .......
  11. 11. Great ... but
  12. 12. TDD and puppet modules ●Write the docs first (README.md) ● Explain your parameters ● Describe the defaults ● What is the function of your module ● What is it intended behaviour ●Write the first test from the README ●Run the tests, all should fail ●Write just enough code to pass the test ●Refactor and reiterate the process (Ted Arroway: Small moves, Ellie, small moves. [Contact] )
  13. 13. The right tools for the right job ● http://www.rvm.io – Switch easily between ruby version ● Rspec-puppet – Written by tim : ● http:/rspec-puppet.com ● https://github.com/rodjek/rspec-puppet ● Puppet module skeleton – https://github.com/ghoneycutt/puppet-module-skeleton – https://github.com/garethr/puppet-module-skeleton – ...
  14. 14. TDD and rspec-puppet ● Testing against the compiled catalog – Are the right resources in the catalogue – With the right attributes ● Is the rspec a duplicate of the manifest code – When you start – yes, because we start simple – But we can copy/paste ? Right !! – Refactoring a basic module shows already the benefits. ● Adding parameters ● Adding logic (eg. Support for multiple OS) ● ... ● Puppet modules with proper rspec test are better candidates ● It should/will become common to do PR including rspec tests
  15. 15. Hands on TDD ● Based on the TDD tutorial Garrett Honeycutt – https://github.com/ghoneycutt/learnpuppet-tdd-vagrant – https://github.com/ghoneycutt/puppet-module-skeleton ● Why ? – Followed the TDD session on LOADays – Everything is configured out of the box – Easy to start doing it the right way – Garrett learned me puppet
  16. 16. Hands on TDD – the setup ● The module directory tree [root@puppet motd]# tree -a . |-- .fixtures.yml |-- Gemfile |-- .gitignore |-- LICENSE |-- manifests | `-- init.pp |-- Modulefile |-- Rakefile |-- README.md |-- spec | |-- classes | | `-- init_spec.rb | `-- spec_helper.rb `-- .travis.yml
  17. 17. Hands on TDD – the setup ● puppet generate module witjoh-motd ● mv witjoh-motd motd ● Rakefile [root@puppet motd]# rake -T rake build # Build puppet module package rake clean # Clean a built module package rake coverage # Generate code coverage information rake help # Display the list of available rake tasks rake lint # Check puppet manifests with puppet-lint / Run puppet-lint rake spec # Run spec tests in a clean fixtures directory rake spec_clean # Clean up the fixtures directory rake spec_prep # Create the fixtures directory rake spec_standalone # Run spec tests on an existing fixtures directory rake validate # Validate manifests, templates, and ruby files
  18. 18. Hands on TDD – the setup ● spec_helper.rb – Code that is run before your spectest – Configures your spec testing environment [root@puppet spec]# cat spec_helper.rb require 'rubygems' require 'puppetlabs_spec_helper/module_spec_helper'
  19. 19. Hands on TDD – the setup ● .fixtures.yml – Catalog dependencies are taken care off ● Resolves dependencies to other modules ● Creates symlink to own module ● (does not support metadata.json from forge modules) [root@puppet motd]# cat .fixtures.yml fixtures: repositories: stdlib: repo: 'git://github.com/puppetlabs/puppetlabs-stdlib.git' ref: '3.2.0' symlinks: motd: "#{source_dir}"
  20. 20. A Simple TDD Session workflow ● Write README first – Explain the function of your module – Parameters ● Default values ● Valid values ● Write the test based on the readme ● Write the code – Just enough code to pass the test ● Refactor and add more stuff –
  21. 21. Hands on TDD – the test ● First test – <module >/spec/classes/init_spec.rb
  22. 22. Rake validate [root@puppet classes]# rake validate (in /root/demos/motd) puppet parser validate --noop manifests/init.pp ruby -c spec/classes/init_spec.rb Syntax OK ruby -c spec/spec_helper.rb Syntax OK [root@puppet classes]#
  23. 23. Hands on TDD – init_spec.rb require 'spec_helper' describe 'motd' do context 'with defaults for all parameters' do it { should contain_class('motd') } it { should contain_file('motd').with({ 'ensure' => 'file', 'path' => '/etc/motd', 'owner' => 'root', 'group' => 'root', 'mode' => '0644', 'content' => nil, }) } end end
  24. 24. Hands on TDD – Rake Spec
  25. 25. Hands on TDD – The code # == Class: motd # # Module to manage motd # class motd { file { 'motd': ensure => 'file', path => '/etc/motd', owner => 'root', group => 'root', mode => '0644', content => undef, } }
  26. 26. Hands on TDD – The test
  27. 27. More rspec describe 'with path specified' do context 'as a valid path' do let(:params){{ :path => '/usr/local/etc/motd'}} it { should contain_file('motd').with({ 'path' => '/usr/local/etc/motd', }) } end context 'as an invalid path' do let(:params) { { :path => 'invalid/path' } } it 'should fail' do expect { should contain_class('motd') }.to raise_error(Puppet::Error) end end end
  28. 28. More rspec ['666','66666','invalid',true].each do |mode| context "as invalid value #{mode}" do let(:params) { { :motd_mode => mode } } it 'should fail' do expect { should contain_class('motd') }.to raise_error(Puppet::Error,/^motd::mode must be a four digit string./) end end end
  29. 29. # package it { should contain_package('ntp_package').with({ ... }) } # file it { should contain_file('ntp_config').with({ ... 'require' => 'Package[ntp]', }) } # service it { should contain_service('ntp_service').with({ ... 'subscribe' => 'File[ntp_config]', })
  30. 30. More rspec # check for a specific line it { should contain_file('ntp_conf').with_content(/^tinker panic 0$/) } # Check that some content is not include it { should_not contain_file('ntp_conf').with_content(/^tinker panic 0$/) }
  31. 31. More rspec context 'with default values for parameters on EL 6' do let(:facts) do { :osfamily => 'RedHat', :lsbmajdistrelease => '6', } end end
  32. 32. More rspec – defined resources # spec/defines/mkdir_p_spec.rb require 'spec_helper' describe 'common::mkdir_p' do context 'should create new directory' do let(:title) { '/some/dir/structure' } it { should contain_exec('mkdir_p-/some/dir/structure').with({ 'command' => 'mkdir -p /some/dir/structure', 'unless' => 'test -d /some/dir/structure', }) } end
  33. 33. More rspec – defined resources context 'should fail with a path that is not absolute' do let(:title) { 'not/a/valid/absolute/path' } it do expect { should contain_exec('mkdir_p- not/a/valid/absolute/path').with({ 'command' => 'mkdir -p not/a/valid/absolute/path', 'unless' => 'test -d not/a/valid/absolute/path', }) }.to raise_error(Puppet::Error) end end end
  34. 34. What should be tested ● All resources should be in the catalog – 100% code coverage ● Parameters – Proper defaults – Setting params, does that work ? – Logic of params – Parameter validation
  35. 35. What should be tested ● Module logic – Based on facts (eg: ::osfamily) – Multiple os support ● Dynamic content – Test your templates
  36. 36. Unit testing is the beginning ● Integration testing ● Acceptance testing ● ....
  37. 37. ?

×