7. Why test?
7
• Confidence to change things
• Know when you break something before deploying it
• Quickly test a matrix of Puppet and Ruby versions
8. Why test?
8
• Confidence to change things
• Know when you break something before deploying it
• Quickly test a matrix of Puppet and Ruby versions
• Test all OS’s without having to deploy it everywhere
9. Why test?
9
• Confidence to change things
• Know when you break something before deploying it
• Quickly test a matrix of Puppet and Ruby versions
• Test all OS’s without having to deploy it everywhere
• Fast feedback
10. Why test?
10
• Confidence to change things
• Know when you break something before deploying it
• Quickly test a matrix of Puppet and Ruby versions
• Test all OS’s without having to deploy it everywhere
• Prevent regression of old problems
• Fast feedback
11. Why test?
11
• Confidence to change things
• Know when you break something before deploying it
• Quickly test a matrix of Puppet and Ruby versions
• Test all OS’s without having to deploy it everywhere
• Prevent regression of old problems
• Fast feedback
• Even in an agile world, we still have design specs.
12. Why test first?
• Puts a focus on what you want to accomplish.
• Documents the functionality that you care about.
• Makes you think about your design.
• Save time by building the minimum viable product first.
• You can refactor later.
12
13. What to test?
• Each parameter
• Each resource
• Ensure that failure occurs when that’s expected
• Conditional logic
13
14. What is actually
tested?
• Catalog is compiled with inputs such as setting values for
facts and parameters
• We test that things are or are not in the catalog
• Simple :)
14
16. What is 1.0.0
• README explains all parameters
• Passes lint
• Works with at least Ruby 1.8.7, 1.9.3, and 2.0.0
• Validates params
• Tests all params
• Tests all flows in logic
16
17. approach to
writing modules
• Write the README first, explaining all of your parameters and their
valid values and their default values.
• Add all of the parameters to your manifests with default values from
the README.
• Write the tests from the README.
• Write just enough code to get your tests to pass.
• Refactor as necessary.
17
21. Get VM
• Install VirtualBox - https://www.virtualbox.org/
• Install Vagrant - http://www.vagrantup.com/
• git clone https://github.com/ghoneycutt/learnpuppet-tdd-vagrant
• cd learnpuppet-tdd-vagrant
• vagrant up
21
22. Testing tools
• Only if you are not using the providedVM
$ sudo gem install -V puppet-lint rspec rspec-puppet
puppetlabs_spec_helper --no-ri --no-rdoc
• https://github.com/puppetlabs/puppet-syntax-vim
• https://github.com/puppetlabs/puppet-syntax-emacs
22
40. testing params
• Each attribute of the file resource should be configurable
through params.
• Let’s test for values that should should work as well as what
should produce an error.
40
41. testing paramsdescribe '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
41
42. testing file content
describe 'with content parameter specified' do
let(:params) { { :content => "Welcome to
puppet.learnpuppet.comnnHave Fun!n" } }
!
!
it {
should contain_file('motd').with_content(
%{Welcome to puppet.learnpuppet.com
!
Have Fun!
})
}
end
42
43. reading tests
$ grep -ie describe -e context spec/classes/init_spec.rb
describe 'motd' do
context 'with default values for all parameters' do
describe 'with motd_file parameter specified' do
context 'as a valid path' do
context 'as an invalid path' do
describe 'with motd_content parameter specified' do
43
44. Exercise
Test all params
• All attributes of file resource should be configurable.
• Write tests first.
• Then add code to the module.
44
45. four digit mode
describe 'with motd_mode specified' do
context 'as a valid four digit entry' do
let(:params) { { :mode => '0755' } }
!
it {
should contain_file('motd').with({
'mode' => '0755',
})
}
end
!
context 'as an invalid three digit entry' do
let(:params) { { :mode => '755' } }
!
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
45
46. for loops
['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
46
47. Exercise
Validate mode
• Validate mode with validate_re()
https://github.com/puppetlabs/puppetlabs-stdlib/tree/3.2.0#validate_re
• Test your regex at http://rubular.com/
47
48. resource relationships# 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]',
})
}
48
49. file content
# check for a specific line
!
it { should contain_file('ntp_conf').with_content(/^tinker panic 0$/) }
49
50. file content
# what if the whole line is optional?
# in this case we test that it is not present
!
it { should_not contain_file('ntp_conf').with_content(/^tinker panic 0$/) }
50
51. Exercise
ntp module
• Use the last few slides to guide you on a module for NTP
• Do the minimum amount of work to get the tests to pass.
• Copy /etc/ntp.conf to your module as a starting place
51
52. specify facts
context 'with default values for parameters on
EL 6' do
let(:facts) do
{ :osfamily => 'RedHat',
:lsbmajdistrelease => '6',
}
end
end
52
53. Exercise
add OS to ntp
• Add support for another OS.This OS should have at least a
different name for the package or service.
53
58. Test functions
# lib/puppet/parser/functions/yell.rb
module Puppet::Parser::Functions
newfunction(:yell, :type => :rvalue, :doc => <<-EOS
Takes one argument, a string to be capitalized. Returns the
string in
all caps.
EOS
) do |args|
raise(Puppet::ParseError, "yell(): Wrong number of arguments " +
"given (#{args.size} for 1)") if args.size != 1
args[0].upcase
end
end
58
59. Test functions
# spec/functions/yell_spec.rb
require 'spec_helper'
describe 'yell' do
it 'should run with correct number of arguments (1)' do
should run.with_params('hello world').and_return('HELLO WORLD')
end
!
it 'should fail with no arguments' do
should run.with_params().and_raise_error(Puppet::ParseError)
end
!
it 'should fail with more than one argument (2)' do
should run.with_params('too','many').and_raise_error(Puppet::ParseError)
end
end
59
60. Defines# 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
!
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
60
61. Exercise
Defines
• Create a define,‘say’, that takes a param,‘msg’ or if msg is not sent,
use the title and pass that to a notify{} resource.
• Write tests first, then write the define.
• Bonus to create your own function to run on the msg, such as
making it all lower case or l33t sp34k.
61