Monday, December 23, 2013

Puppet rspec testing and hiera

Rspec testing your puppet modules supports you in having stable and functional modules.
For a couple of weeks there is hiera integration in rspec-puppet.



Basic Spec setup


You will need a couple of files for puppet module rspec testing:

Gemfile
# Install via
# bundle install --path vendor/gems
#
source "https://rubygems.org"

gem "mocha", :require => false
gem 'puppet',       '>= 3.1.1'
gem 'puppet-lint'
gem 'facter',       '>= 1.6.10'
gem 'rspec-puppet', :git => "https://github.com/rodjek/rspec-puppet.git"
gem 'rake',         '>= 0.9.2'
gem 'puppetlabs_spec_helper', '0.3.0'
gem 'test-unit'
Rakefile
require 'rake'
require 'rake/tasklib'
require 'rspec/core/rake_task'
require 'rubygems'
require 'puppetlabs_spec_helper/rake_tasks'
require 'puppet-lint'

desc "Run the tests"
RSpec::Core::RakeTask.new(:test) do |t|
  t.rspec_opts = ['--color', '-f d']
  t.pattern = 'spec/*/*_spec.rb'
end

desc 'Run puppet-lint on the one manifests'
task :onelint do
  PuppetLint.configuration.with_filename = true

  linter = PuppetLint.new
  matched_files = FileList['spec/fixtures/modules/one/manifests/**/*.pp']

  matched_files.to_a.each do |puppet_file|
    linter.file = puppet_file
    linter.run
  end

  fail if linter.errors? || (
    linter.warnings? && PuppetLint.configuration.fail_on_warnings
    )
end

task :default => [:spec_prep, :test, :onelint, :spec_clean]
spec/spec_helper.rb
require 'test/unit'
require 'mocha/setup'
require 'puppetlabs_spec_helper/module_spec_helper'
require 'puppet/indirector/hiera'
require 'hiera'


fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures'))

# include common helpers
support_path = File.expand_path(File.join(File.dirname(__FILE__), '..',
                                          'spec/support/*.rb'))
Dir[support_path].each {|f| require f}

RSpec.configure do |c|
  c.config = '/doesnotexist'
  c.manifest_dir = File.join(fixture_path, 'manifests')
  c.mock_with :mocha
end

Fixtures


If your module require other modules (e.g. stdlib functions) you have to include them using fixtures repositories. Add your own module as a symlink:

.fixtures.yaml
fixtures:
    repositories:
        stdlib: "https://github.com/puppetlabs/puppetlabs-stdlib.git"
        concat: "https://github.com/puppetlabs/puppetlabs-concat.git"
    symlinks:
        <your module name>: "#{source_dir}"
 This will create all required directory structures in spec/fixtures/modules.

Hiera Configuration

Add a hiera.yaml and a hiera data file to your spec/fixtures:

spec/fixtures/hiera/hiera.yaml
---
:backends:
  - yaml
:hierarchy:
  - test

:yaml:
    :datadir: 'spec/fixtures/hiera'

spec/fixtures/hiera/test.yaml
---
myvariable: 'myvalue'

Hiera Integration

Only minor changes are required to integrate hiera lookups into your spec tests.
The following example is taken from my stdmodule on github (https://github.com/tuxmea/tuxmea-stdmodule.git)

spec/classes/<classname>_spec.rb
require 'spec_helper'
hiera_file = 'spec/fixtures/hiera/hiera.yaml'   # <- required to find hiera configuration file

# test our main class (init.pp)
describe 'stdmodule', :type => :class  do     # <- set the class name

    # test without hiera lookup (using class defaults)
    context 'without hiera data' do
        it { should contain_class('stdmodule') }                        # <- check for classes included
        it { should contain_class('stdmodule').with_myparam('foo') }    # <- check for classes with parameters
        it { should contain_class('stdmodule::basic') }                 # <- check for subclasses
        it { should contain_class('stdmodule::package_file_service') }
        it { should contain_stdmodule__defines('foobar_define') }       # <- check for defines
    end

    # test with explicit params given on class definition
    context 'with explicit data' do
        let(:params) {{                                                 # <- set explizit params
            :myparam => 'newvalue'
        }}
        it { should contain_class('stdmodule').with_myparam('newvalue') }
    end

    # test with explicit hiera lookup
    context 'with explicit hiera lookup' do
        # set hiera_config
        let (:hiera_config) { hiera_file }

        hiera = Hiera.new(:config => hiera_file)                        # <- use Hiera ruby class
        variable = hiera.lookup('stdmodule::myparam', nil, nil)         # <- do hiera lookup
        let (:params) {{
            :myparam => variable                                        # <- set parameter to hiera lookup
        }}
        it { should contain_class('stdmodule').with_myparam(variable) }
    end
end

1 comment:

  1. With all respect given to your efforts, this is actually wrong...

    Compare what you have done under the context "with explicit data" to what you have done under the context "with explicit hiera lookup." They are actually one and the same.

    In both cases, you explicitly passed a parameter into the class under test. It does not matter that in the second case, the _test code_ got the value for that parameter from hiera. The method of providing the data _to_ the class under test is no different in the two contexts. This means you have tested the same thing twice.

    What does _not_ get covered in this test is the scenario where no parameters are explicitly passed to the class under test and puppet take it upon itself to try and resolve values for each parameter from hiera. _That_ is missing from here.

    All that being said, I am having a great deal of difficulty figuring out how to actually make the missing test scenario work as described. The documentation is spotty and I actually found this blog post of yours while trying to find the answer.

    ReplyDelete