Chapter 17. Testing Modules

Sad to say, but not many modules include good tests. Good tests help keep embarrassing bugs from going out. Tests can save you a lot of time, avoiding the exhaustive debugging of an issue in production that turns out to be a wrong type used, or a similar mistake.

This chapter will teach you how to add good tests to your modules. When I got started, I struggled a lot due to a lack of good examples. This chapter provides good examples of each type of test you should be doing. It’s my intention that you’d be able to use the examples provided here like tinker toys, and build a good set of tests for your modules without much effort.

This chapter won’t provide exhaustive documentation of rspec or beaker, the testing tools of choice for the Puppet ecosystem. However, you should be able to build a good foundation of tests from what we cover in this chapter.

Let’s get started by setting up your testing tools.

Installing Dependencies

The first time you set up to do testing, you’ll need to install some specific software.

Installing Ruby

You can use the Ruby version that comes with your operating system. If you are using the Vagrant testing setup documented in this book, it is easy to install Ruby into the system packages:

[vagrant@client ~]$ sudo yum install -y ruby-devel rubygems rake libxml2-devel

Guides for installing Ruby can be found in Appendix C.

Install the bundler gem for local installation of necessary dependencies:

$ sudo gem install bundler --no-ri --no-rdoc
Fetching: bundler-1.11.1.gem (100%)
Successfully installed bundler-1.11.1
1 gem installed

Adding Beaker

Add the following lines to the Gemfile in the module directory:

gem 'beaker-rspec', :require => false
gem 'pry',          :require => false

These lines will ensure that Beaker is installed along with the other dependencies in the next step. Beaker gem dependencies require these development libraries to compile binary extensions:

[vagrant@client ~]$ sudo yum install -y gcc-d++ libxml2-devel libxslt-devel

Bundling Dependencies

If you haven’t done this already, you’ll need to install the puppetlabs_spec_helper and other dependency gems. The best way to do this is to run the bundler install command within the module directory. bundle will read the Gemfile and pull in rspec, rspec-puppet, beaker, and all of their dependencies. These are testing and template creation tools that simplify test creation:

[vagrant@client puppet]$ bundler install
Fetching gem metadata from https://rubygems.org/..........
Fetching version metadata from https://rubygems.org/..
Resolving dependencies...
Installing rake 11.1.1
Installing CFPropertyList 2.2.8
Using diff-lcs 1.2.5
...snip a long list of gems...
Installing rspec 3.4.0
Installing puppet 4.10.9
Installing rspec-puppet 2.3.2
Installing puppetlabs_spec_helper 1.1.1
Installing beaker 2.37.0
Installing beaker-rspec 5.3.0
Bundle complete! 6 Gemfile dependencies, 95 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
Note
Don’t use sudo when running bundler. Its purpose is to vendor the gems locally in ~/.gems/, without affecting the system gems.

Preparing Your Module

The next step is to set up your module for testing. We’ll have to modify a few files to use the best tools for this.

Defining Fixtures

Create a .fixtures.yml file to define the testing fixtures (dependencies) and where to acquire them for testing purposes. The information in this file should duplicate the dependencies in metadata.json.

The top of the file should always be the same. This tells the testing frame to copy the current module from its directory:

fixtures:
  symlinks:
    puppet: "#{source_dir}"

Then define each dependency for your module and the minimum version you support. You can list their names on the Puppet Forge or their GitHub URL. The following two examples will have similar effects. From the Forge:

  forge_modules:
    stdlib:
      repo: "puppetlabs/stdlib"
      ref: 4.5.1

From GitHub:

  repositories:
    stdlib:
      repo: "git://github.com/puppetlabs/puppetlabs-stdlib"
      ref: "4.5.1"

If you are testing development of multiple modules, you may want to use symlinks to the source tree for each. Assuming the dependency is in the same directory structure:

  symlinks:
    some_dependency: "#{source_dir}/../some_dependency"

Test that dependency setup worked properly like so:

$ rake spec
(in /etc/puppetlabs/code/environments/test/modules/puppet)
Notice: Preparing to install into
  /etc/puppetlabs/code/environments/test/modules/puppet/spec/fixtures/modules ...
Notice: Downloading from https://forgeapi.puppetlabs.com ...
Notice: Installing -- do not interrupt ...
/etc/puppetlabs/code/environments/test/modules/puppet/spec/fixtures/modules
 puppetlabs-stdlib (v3.2.1)
/usr/bin/ruby -I/usr/lib/ruby/gems/1.8/gems/rspec-support-3.2.2/lib:...
No examples found.

Finished in 0.00027 seconds (files took 0.04311 seconds to load)
0 examples, 0 failures

This shows that all fixtures (dependencies) were installed, but no examples (tests) were available. Let’s start building one now.

Defining RSpec Unit Tests

Now let’s build some tests for the module. We know, few people think that building tests is fun work—but it is important work that will save you time and effort down the road.

You should follow these guidelines for creating useful tests:

  • Test every input parameter.
  • Test every file, package, and service name.
  • Test every variation in implementation your module is designed to handle.
  • Test for implicit choices based around operating system or other environmental tests.
  • Test for invalid input as well as valid input.

Let’s look at some examples that test each one of these situations.

Defining the Main Class

Within your module directory, change into the spec/classes/ directory. Inside this directory, create a file named <modulename>_spec.rb.

[vagrant@client puppet]$ cd spec/classes
[vagrant@client classes]$ $EDITOR puppet_spec.rb

First, we will define a test where the module test builds (compiles) a catalog successfully with the default options:

require 'spec_helper'

describe 'puppet', :type => 'class' do

  context 'with defaults for all parameters' do
    it { is_expected.to contain_class('puppet') }
    it { is_expected.to contain_class('puppet::params') }
    it { is_expected.to compile.with_all_deps }
  end
end

Let’s go ahead and run the testing suite against this very basic test:

[vagrant@client puppet]$ rake spec
(in /etc/puppetlabs/code/modules/puppet)
ruby -I/opt/puppetlabs/puppet/lib/ruby/gems/2.1.0/gems/rspec-support-3.2.2/lib:
..

Finished in 10.22 seconds (files took 0.56629 seconds to load)
2 examples, 0 failures

Now that our basic test passed, let’s go on to start checking the input parameters.

Passing Valid Parameters

What if we expand the tests to include every possible input value? Rather than repeating each test with a different value, we use Ruby loops to iteratively build each of the tests from an array of values:

['1','0'].each do |repo_enabled|
  ['emerg','crit','alert','err','warning','notice','info'].each do |loglevel|
    context "with repo_enabled = #{repo_enabled}, loglevel #{loglevel}" do
      let :params do
        {
          :repo_enabled => repo_enabled,
          :loglevel     => loglevel,
        }
      end

      it { is_expected.to
        contain_package('puppet-agent').with({'version' => '1.10.9-1'})
      }
    end
  end
end

Whoa, look at that. We added 15 lines of code and yet it’s performing 36 more tests:

[vagrant@client puppet]$ rake spec
(in /etc/puppetlabs/code/modules/puppet)
ruby -I/opt/puppetlabs/puppet/lib/ruby/gems/2.1.0/gems/rspec-support-3.2.2/lib:

......................................

Finished in 10.53 seconds (files took 0.56829 seconds to load)
38 examples, 0 failures

Failing Invalid Parameters

Always test to ensure incorrect values fail. This example shows two tests that are intended to fail:

  context 'with invalid loglevel' do
    let :params do
      {
        :loglevel => 'annoying'
      }
    end

    it { is_expected.to compile.with_all_deps }
  end

  context 'with invalid repo_enabled' do
    let :params do
      {
        :repo_enabled => 'EPEL'
      }
    end

    it { is_expected.to compile.with_all_deps }
  end

Run the tests to see what error messages are kicked back:

[vagrant@client puppet]$ rake spec
Failures:

1) puppet4 with invalid loglevel should build a catalog w/o dependency cycles
   Failure/Error: should compile.with_all_deps
     error during compilation: Parameter loglevel failed on Class[Puppet]:
       Invalid value "annoying". Valid values are debug, info, notice, warning,
       err, alert, emerg, crit, verbose.  at line 1
   # ./spec/classes/puppet_spec.rb:52:in 'block (3 levels) in <top (required)>'

2) puppet4 with invalid repo_enabled should build a catalog w/o dependency cycles
   Failure/Error: should compile.with_all_deps
     error during compilation: Expected parameter 'repo_enabled' of
       'Class[Puppet]' to have type Enum['0', '1'], got String at line 1
   # ./spec/classes/puppet_spec.rb:65:in 'block (3 levels) in <top (required)>'

Finished in 10.81 seconds (files took 0.57989 seconds to load)
40 examples, 2 failures

Now let’s change the expect lines to accept the error we expect:

    it do
      is_expected.to compile.with_all_deps.and_raise_error(Puppet::Error,
        /Invalid value "annoying". Valid values are/
      )
    end

and the following for the test of repo_enabled:

    it do
      is_expected.to compile.with_all_deps.and_raise_error(Puppet::Error,
        /Expected parameter 'repo_enabled' .* to have type Enum/)
      )
    end

Now when you run the tests, you will see the tests were successful because the invalid input produced the expected error.

Testing File Creation

You can test to ensure that a file resource is created. The simplest form is:

  it { is_expected.to contain_file('/etc/puppetlabs/puppet/puppet.conf') }

Now, this only checks that the file exists, and not that it was modified correctly by the module. Test resource attributes using this longer form:

  it do
    is_expected.to contain_file('/etc/puppetlabs/puppet/puppet.conf').with({
      'ensure' => 'present',
      'owner'  => 'root',
      'group'  => 'root',
      'mode'   => '0444',
    })
  end

Finally, you can also check the content against a regular expression. Here’s an example where we pass in a parameter, and then want to ensure it would be written to the file:

  let :params do
  {
    :loglevel => 'notice',
  }
  end

  it do
    is_expected.to contain_file('/etc/puppetlabs/puppet/puppet.conf').with_content({
      /^s*loglevels*=s*notice/
    })
  end

Validating Class Inclusion

You can test to ensure that a dependent class was loaded:

  it { is_expected.to contain_class('puppet::_config') }

When testing a defined type, set a title for the defined type to be passed during the test:

  let(:title) { 'mytype_testing' }

Using Facts in Tests

Some manifests or tests may require that certain facts are defined properly. Inside the context block, define a hash containing the fact values you want to have available in the test:

let :facts do
  {
    :osfamily => 'RedHat',
    :os       => {
      'family' => 'RedHat',
      'release' => { 'major' => '7', 'minor' => '2' }
    },
  }
end

By default, the hostname, domain, and fqdn facts are set from the fully qualified domain name of the host. To adjust the node name and these three facts for testing purposes, add this to the test:

    let(:node) { 'webserver01.example.com' }

Using Hiera Input

Within your module directory, change to the spec/fixtures/ directory. Inside this directory, create a subdirectory named hiera, containing a valid hiera.yaml file for testing:

[vagrant@client puppet]$ cd spec/fixtures
[vagrant@client puppet]$ mkdir hiera
[vagrant@client classes]$ $EDITOR hiera/hiera.yaml

You can change anything you want that is valid for Hiera in this configuration file, except for the datadir, which should reside within the fixtures path. Unless you desire a specific change, the following file could be used unchanged in every module:

# spec/fixtures/hiera/hiera.yaml
---
:backends:
  - yaml
:yaml:
  :datadir: /etc/puppetlabs/code/hieradata
:hierarchy:
  - os/"%{facts.os.family}"
  - common

Now, add the following lines to a test context within one of the class spec files:

  let(:hiera_config) { 'spec/fixtures/hiera/hiera.yaml' }
  hiera = Hiera.new( :config => 'spec/fixtures/hiera/hiera.yaml' )

Now create your Hiera input files. The only necessary file is spec/fixtures/hiera/common.yaml. The others can be added only when you want to test things:

---
puppet::loglevel      : 'notice'
puppet::repo_enabled  : '1'
puppet::agent::status : 'running'
puppet::agent::enabled: true

You can use this Hiera data to configure the tests:

  let :params do
    {
      :repo_enabled => hiera.lookup('puppet::repo_enabled',nil,nil),
      :loglevel     => hiera.lookup('puppet::loglevel',nil,nil),
    }
  end

This configuration allows you to easily test the common method of using Hiera to supply input parameters for your modules.

Defining Parent Class Parameters

In some situations, your module will depend upon a class that requires some parameters to be provided. You cannot set parameters or use Hiera for that class, because it is out of scope for the current class and test file.

The workaround is to use a pre_condition block to call the parent class in resource-style format. Pass the necessary parameters for testing as parameters for the resource declaration, and this module instance will be created before your module is tested.

Here is an example from my mcollective module, which had to solve this exact problem:

describe 'mcollective::client' do
  let(:pre_condition) do
    'class { "mcollective":
      hosts           => ["middleware.example.net"],
      client_password => "fakeTestingClientPassword",
      server_password => "fakeTestingServerPassword",
      psk_key         => "fakeTestingPreSharedKey",
    }'
  end

...tests for the mcollective::client class...

Testing Functions

Unit tests should be created for any functions added by the module. Each function test should exist in a separate file, stored in the spec/functions/ directory of the module, and named for the function followed by the _spec.rb extension.

At a bare minimum, the test should ensure that:

  • The function fails for insufficient or too many values.
  • Given an expected input, it produces an expected output.

Here is an example that should work for our make_boolean() example:

#! /usr/bin/env ruby -S rspec
require 'spec_helper'

describe 'make_boolean' do
  describe 'should raise a ParseError if there is less than 1 argument' do
    it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) }
  end

  describe "should convert string '0' to false" do
    it { is_expected.to run.with_params("0").and_return(false) }
  end
end

Adding an Agent Class

Within the spec/classes/ directory, create a file named agent_spec.rb. This is an exercise for you. Build the agent class, testing every valid and invalid input just like we did for the Puppet class.

For this, we simply want to test that the package, config file, and service resources are all defined:

require 'spec_helper'

describe 'puppet::agent', :type => 'class' do

  context 'with defaults for all parameters' do
    it do
      is_expected.to contain_package('puppet-agent').with({ 'version' => 'latest' })
      is_expected.to contain_file('puppet.conf').with({ 'ensure' => 'file' })
      is_expected.to contain_service('puppet').with({ 'ensure' => 'running' })
    end

    it { is_expected.to compile.with_all_deps }
  end
end

We have demonstrated how to build tests. Now, you should build out more tests for valid and invalid input.

Testing Other Types

Every object type provided by a module can be tested. Place tests for the other types in the directories specified in Table 17-1.

Table 17-1. Directories in which to place other tests
Type Directory
Class spec/classes/
Defined resource type spec/defines/
Functions spec/functions/
Node differences spec/hosts/

Creating Acceptance Tests

The rspec unit tests discussed earlier in this chapter validate individual features by testing the code operation in the development environment. Rspec tests provide low-cost, easy-to-implement code validation.

In contrast, the Beaker test harness spins up (virtual) machines to run platform-specific acceptance tests against Puppet modules.

Beaker creates a set of test nodes (or nodeset) running each operating system and configuration you’d like to perform system tests for. Beaker tests are significantly slower and more resource-intensive than rspec tests, but they provide a realistic environment test that goes far beyond basic code testing.

Installing Ruby for System Tests

As Beaker will need to run vagrant commands to create virtual machines to run the test suites on, you’ll need to run Beaker on your development system rather than one of the virtual machines you’ve been using so far. Following are the steps to set up and appropriate testing environment on your system.

  1. Follow the instructions for installing Ruby on your desktop operating system. Directions for installing Ruby on different operating systems can be found in Appendix C.
  2. Use the gem install bundler command to install Bundler.
  3. Change into the module directory and run bundle install to install the test dependencies.

Defining the Nodeset

Create a directory within the module to contain the nodesets used for testing:

[vagrant@client puppet]$ mkdir -p spec/acceptance/nodesets

Create a file name default.yml within this directory. This file should contain YAML data for nodes to be created for the test. The following example will create a test node using the same Vagrant box utilized within this book.

HOSTS:
  centos-7-x64:
    roles:
	  - agent
    platform: el-7-x86_64
    box: puppetlabs/centos-7.2-64-nocm
    hypervisor: vagrant
    vagrant_memsize: 1024

Create additional nodeset files within this directory for all platforms that should be tested. Sample nodeset files for different operating systems can be found at Example Vagrant Hosts Files.

Note
Beaker is capable of using many virtualization systems. However, configuring these is beyond the scope of this book. Vagrant is sufficient for most testing purposes.

Configuring the Test Environment

Create a file named spec/spec_helper_acceptance.rb within the module directory. This file defines the tasks needed to prepare the test system.

The following example will ensure Puppet and the module dependencies are installed:

require 'beaker-rspec'
require 'pry'

step "Install Puppet on each host"
install_puppet_agent_on( hosts, { :puppet_collection => 'pc1' } )

RSpec.configure do |c|
  # Find the module directory
  module_root = File.expand_path( File.join( File.dirname(__FILE__), '..') )

  # Enable test descriptions
  c.formatter = :documentation

  # Configure all nodes in nodeset
  c.before :suite do
    # Install module and dependencies
    puppet_module_install(
      :source      => module_root,
      :module_name => 'puppet',
    )
    hosts.each do |host|
      # Install dependency modules
      on host, puppet('module', 'install', 'puppetlabs-stdlib'),
         { :acceptable_exit_codes => [0,1] }
      on host, puppet('module', 'install', 'puppetlabs-inifile'),
         { :acceptable_exit_codes => [0,1] }
    end
  end
end

This is generally a consistent formula you can use with any module. The adjustments specific to this module have been bolded in the preceding example.

Creating an Acceptance Test

The tests are written in rspec, like the unit tests created in the previous section. However ServerSpec tests are also available and can be used in combination with rspec tests.

For most modules that set up services, the first test should be an installation test to validate that it installs with the default options, and that the service is properly configured.

Create a file named spec/acceptance/installation_spec.rb within the module directory. The following example defines some basic tests to ensure that the package is installed and that the service runs without returning an error:

require 'spec_helper_acceptance'

describe 'puppet class' do
  context 'default parameters' do
    # Using puppet_apply as a helper
    it 'should install with no errors using default values' do
      puppetagent = <<-EOS
        class { 'puppet::agent': }
      EOS

      # Run twice to test idempotency
      expect( apply_manifest( puppetagent ).exit_code ).to_not eq(1)
      expect( apply_manifest( puppetagent ).exit_code ).to eq(0)
    end

    describe package('puppet-agent') do
      it { should be_installed }
    end

    describe file('/etc/puppetlabs/puppet/puppet.conf') do
      it { should be_a_file }
    end

    describe service('puppet') do
      it { should be_enabled }
    end

  end
end

You can and should create more extensive tests that utilize different input scenarios for the module. Each test should be defined as a separate file in the spec/acceptance/ directory, with a filename ending in _spec.rb.

You can find more information, including details about writing good tests, in these places:

  • The best documentation for Beaker can be found at puppetlabs/beaker on GitHub.
  • The best documentation for writing rspec tests can be found at rspec-core on the rspec.info site.
  • The best documentation for writing serverspec tests can be found at serverspec resource types on the serverspec.org site.

Running Acceptance Tests

To spin up virtual machines to run the test, you’ll need to run the acceptance tests from a system that has Vagrant installed. Your personal system will work perfectly fine for this purpose.

Running tests on all nodes

Let’s go ahead and run the entire acceptance test suite:

$ bundle exec rspec spec/acceptance
Hypervisor for centos-7-x64 is vagrant
Beaker::Hypervisor, found some vagrant boxes to create
==> centos-7-x64: Forcing shutdown of VM...
==> centos-7-x64: Destroying VM and associated drives...
created Vagrantfile for VagrantHost centos-7-x64
Bringing machine 'centos-7-x64' up with 'virtualbox' provider...
==> centos-7-x64: Importing base box 'puppetlabs/centos-7.2-64-nocm'...
Progress: 100%
==> centos-7-x64: Matching MAC address for NAT networking...

The test will spin up a Vagrant instance for each node specified in the spec/acceptance/nodeset/ directory. It will run the tests on each of them, and output the status to you, as shown here:

puppet::agent class
  default parameters

    should install with no errors using default values
    Package "puppet-agent"
      should be installed
    File "/etc/puppetlabs/puppet/puppet.conf"
      should be a file
    Service "puppet"
      should be enabled

Destroying vagrant boxes
==> centos-7-x64: Forcing shutdown of VM...
==> centos-7-x64: Destroying VM and associated drives...

Finished in 17.62 seconds (files took 1 minute 2.88 seconds to load)
4 examples, 0 failures

As shown, all tests have completed without any errors.

Troubleshooting Beaker failures

Test failure messages are clear and easy to read. However, they won’t necessarily tell you why something isn’t true. When testing this example, I got the following errors back:

  2) puppet::agent class default parameters
       Package "puppet-agent" should be installed
     Failure/Error: it { should be_installed }
       expected Package "puppet-agent" to be installed

     # ./spec/acceptance/installation_spec.rb:17:in `block (4 levels) in <top>'

Beaker provides extensive debug output, showing every command and its output on the test nodes. Preserve the test system for evaluation by setting the BEAKER_destroy environment variable to no:

$ BEAKER_destroy=no BEAKER_debug=yes bundle exec rspec spec/acceptance

You can isolate debugging to a single host configuration. For example, if there were a nodeset in spec/acceptance/nodeset/centos-6-x86.yml, then the following command would run the tests in debug mode on the CentOS 6 node only:

$ BEAKER_set=centos-6-x86 BEAKER_debug=yes rspec spec/acceptance

Debug output will show the complete output of every command run by Beaker on the node, like so:

* Install Puppet on each host

centos-7-x64 01:36:36$ rpm --replacepkgs -ivh
  Retrieving http://yum.puppetlabs.com/puppetlabs-release-el-7.noarch.rpm
  Preparing...                          ########################################
  Updating / installing...
  puppetlabs-release-7-11               ########################################
  warning: rpm-tmp.KNaXPi: Header V4 RSA/SHA1 Signature, key ID 4bd6ec30: NOKEY

centos-7-x64 executed in 0.35 seconds

Avoid reprovisioning the node each time when you are debugging code. The following process is a well-known debugging pattern:

  1. Run the test but keep the node around:

    $ BEAKER_destroy=no BEAKER_debug=yes rspec spec/acceptance
    
  2. Attempt to fix the test or the manifest.
  3. Rerun the test using the existing node:

    $ BEAKER_destroy=no BEAKER_provision=no BEAKER_debug=yes 
    	bundle exec rspec spec/acceptance
    
  4. Return to step 2 until the test runs cleanly.
  5. Run the test with a fresh provisioning:

    $ rspec spec/acceptance
    

Accessing a Beaker host console

When all else fails, access the console of a host so that you can dig around and determine what happened during the test. At the point where investigation is necessary, add binding.pry to the _spec.rb test file that is failing:

    describe package('puppet-agent') do
      it { should be_installed }
    end
    # Access host console to debug failure
    binding.pry

Then rerun the test:

$ BEAKER_destroy=no bundle exec rspec spec/acceptance
...
From: learning-puppet4/puppet4/spec/acceptance/installation_spec.rb @ line 20 :

    16:     describe package('puppet-agent') do
    17:       it { should be_installed }
    18:     end
    19:     # Make the debug console available
 => 20:     binding.pry
    21:

[1] pry(RSpec::ExampleGroups::PuppetAgentClass::DefaultParameters)>

Pry is a debugging shell for Ruby, similar but more powerful than IRB. Documenting all features is beyond the scope of this book, but the following commands have proven very useful to investigate host state.

You can cat and edit local files directly:

>> cat spec/acceptance/installation_spec.rb
>> edit spec/acceptance/installation_spec.rb

Enter shell-mode to be placed in a local directory. Prefix commands with a period to execute them on the local system:

>> shell-mode
learning-puppet4/puppet4 $ .ls spec/acceptance/
installation_spec.rb   nodesets/

Get a list of hosts in the text, with their index number:

>> hosts.each_with_index do |host,i| print "#{i} #{host.hostname()}
"; end;
1 centos-7-x64

Use the host index to execute a command on the host. Add a trailing semicolon to avoid debugger verbosity:

>> on hosts[0], 'rpm -qa |grep puppet' ;

centos-7-x64 02:50:14$ rpm -qa |grep puppet
  puppet-3.8.4-1.el7.noarch
  puppetlabs-release-7-11.noarch

Whoops, the wrong version of Puppet was installed. The default is still currently to install the old open source version (Puppet v3). Changing the type parameter to aio in the nodeset solved this problem.

For more information about using this debugger, refer to Pry: Get to the Code.

Doing more with Beaker

Beaker can test dozens of other resources, including network interfaces, system devices, logical configurations, and TLS certificates. A complete list of resource types with should and expect examples can be found at ServerSpec Resource Types.

Beaker is a fast-moving project with new features added constantly. Check the Beaker GitHub project for the latest documentation.

Using Skeletons with Testing Features

There are a number of Puppet module skeletons that preinstall frameworks for enhanced testing above and beyond what we’ve covered. You may want to tune the module skeleton you use to include testing frameworks and datasets consistent with your release process.

Place the revised skeleton in the ~/.puppetlabs/opt/puppet/cache/puppet-module/skeleton directory, or specify it on the puppet module generate command line with --module_skeleton_dir=path/to/skeleton.

The following are skeletons I have found useful at one time or another (in their own words):

garethr/puppet-module-skeleton

This skeleton is very opinionated. It’s going to assume you’re going to start out with tests (both unit and system), that you care about the Puppet style guide, test using Travis, keep track of releases and structure your modules according to strong conventions.

puppet-module-skeleton README

This skeleton is popular and recommended by Puppet Labs Professional Services Engineers.

jimdo/puppet-skeleton

The module comes with everything you need to develop infrastructure code with Puppet and feel confident about it.

puppet-skeleton README

This skeleton includes helpers to spin up Vagrant instances and run tests on them.

ghoneycutt/puppet-module-skeleton
At the time this book was written, Garret didn’t provide a README for this skeleton; however, Garret is an active and high-quality contributor to the Puppet community with numerous Puppet Approved modules.
gds-operations/puppet-skeleton

This is a skeleton project for Web Operations teams using Puppet. It ties together a suite of sensible defaults, best current practices, and re-usable code. The intentions of which are two-fold:

  • New projects can get started and bootstrapped faster without needing to collate or rewrite this material themselves.

  • The standardization and modularization of these materials makes it easier for ongoing improvements to be shared, in both directions, between different teams.

puppet-skeleton README

wavesoftware/puppet-os-skeleton

A complete working solution with:

  • Puppet master and agent nodes on Puppet Open Source

  • Spotify Puppet Explorer and PuppetDB

  • Hiera configuration

  • Dynamic Git environments by r10k

  • External puppet modules installation and maintenance by r10k

  • Landrush local DNS

Couple of bootstrap Puppet classes:

  • common::filebucket - use of filebucket on all files

  • common::packages - central packages installation from hiera

  • common::prompt - a Bash command prompt with support for Git and Mercurial

puppet-os-skeleton README

You can find many others by searching for “puppet skeleton” on GitHub. In particular, you can find skeletons specialized for specific application frameworks: OpenStack, Rails, Django, and so on.

Finding Documentation

You may have found a tricky problem not covered by the examples here. At this point, it is best to refer to the original vendor documentation:

Much of this documentation is dated, but still valid. There are open bugs to provide Puppet 4.x-specific documentation, and I will update this section as soon as it is available.

Reviewing Testing Modules

Each class and defined type should have both unit and acceptance tests defined for it. Some good rules of thumb for tests are:

  • Place unit tests for each class in the spec/classes/ directory.
  • Place unit tests for defined types in the spec/defines/ directory.
  • Place unit tests for functions in the spec/functions/ directory.
  • Tests should (at minimum) validate the most common case and default parameter values.

In this chapter, we have discussed how to test modules for:

  • Simple catalog build success with default values
  • Minimum and acceptable values passed in as parameters
  • Creating the resources they were intended to manage
  • Invalid and unacceptable values
  • Providing data using Hiera fixtures
  • Preloading parent modules with required parameters to ensure module dependencies are valid

This has covered the necessary tests that should be included in every module. In the next chapter, we’re going to look at how to publish modules on the Puppet Forge.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset