HomeDevOpsTest Driven Development with RSpec and Chef

Test Driven Development with RSpec and Chef

Test driven development (TDD) ensures that you’ve properly defined your code’s goals, that your code has achieved these goals, and that future changes to the code will not break existing functionality.

Now that we have addressed the fundamental concepts of using Chef data and cookbooks, it’s useful to step through a specific example using the TDD software development process. Please note that Rspec can test the Ruby language and is extended for server specific testing. This introduces us to projects like ServerSpec and ChefSpec, which add functionality with domain specific focus. Another notable benefit of RSpec is the sheer speed in which RSpec tests can be run in memory without the need to launch and provision a full fledged virtual machine (VM). That said, you are welcome to choose a number of other testing frameworks if RSpec doesn’t meet your specific needs.

Step 0: Determine the User Story

In the previous article, we outlined a very simple user story:

As a developer I want to place a file on a server that contains the company slogan.

This type of requirement arises time and time again during web development projects. Example: Google will generate an account specific HTML and require a user to upload said HTML file to their website in order to verify the user has ownership of the domain before the user can begin using services like Google Analytics or Google Webmaster Tools for that domain. The user story listed above is a more generic example of this type of request.

Every user story should have some type of acceptance criteria associated with it, which will then be used to create the specific tests. In this example, the acceptance criteria would be the ability to generate a file at a specific location that contains a specific string of text. Both the location and the string value will need to be defined as variables so that each user of the cookbook can change these values for their specific needs.

Step 1: Create a Failing Test

Once the user story and its acceptance criteria are in place, its time to create a test that will validate the desired outcome for generating the slogan file. To do this, we will need to create an RSpec file and place it within the spec folder on the nmdbase repository. For the company slogan user story, create a file called /spec/slogan_spec.rb and add the following contents to it:


# encoding: utf-8
require 'chefspec'
require 'spec_helper'

describe 'nmdbase::slogan' do
  let(:chef_run) { ChefSpec::Runner.new.converge(described_recipe) }

  it 'Creates a global slogan file.' do
    expect(chef_run).to create_template('/etc/slogan').with(
      user: 'root',
      group: 'root',
      mode: 0644
    )
  end

end

At this time, we have not written any code that would make this test pass. And we can demonstrate that it’s failing by running all RSpec tests using the following command:

bundle exec rake spec

You should see the following failure snippet in the log output:


nmdbase::slogan

================================================================================
Recipe Compile Error
================================================================================

Chef::Exceptions::RecipeNotFound
--------------------------------
could not find recipe slogan for cookbook nmdbase

  Creates a global slogan file. (FAILED - 1)

You will also see the following summary at the end of the run:


Failures:

  1) nmdbase::slogan Creates a global slogan file.
     Failure/Error: let(:chef_run) { ChefSpec::Runner.new.converge(described_recipe) }
     Chef::Exceptions::RecipeNotFound:
       could not find recipe slogan for cookbook nmdbase
     # ./spec/slogan_spec.rb:6:in `block (2 levels) in '
     # ./spec/slogan_spec.rb:9:in `block (2 levels) in '

Finished in 4.13 seconds
28 examples, 1 failure

Failed examples:

rspec ./spec/slogan_spec.rb:8 # nmdbase::slogan Creates a global slogan file.

Sidenote: For more information on how to read and write RSpec tests, please visit the official homepage and consider purchasing The Rspec Book, which is a tremendously valuable resource on the subject matter.

Step 2: Create recipe

With the failing test in place, the next step is to modify the Chef recipe to provide the ability to create the expected file. There are two necessary steps to achieve this: modification of the metadata.rb file (documentation) and the creation of a specific Chef recipe.

To address the documentation requirement, you will need to add the following entry within the metadata.rb file within the nmdbase repo:


provides 'nmdbase::slogan'
recipe 'nmdbase::slogan', 'Provides an example for documentation purposes.'

Next, we will need to create the recipe that will be responsible for generating the file. For this slogan user story, this can be achieved by creating the file /recipe/slogan.rb with the following contents:


# encoding: utf-8
#
# Cookbook Name:: nmdbase
# Recipe:: ldap
#
# Author:: FIRSTNAME LASTNAME
# Copyright:: INSERT COPYRIGHT
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
template node['nmdbase']['slogan']['path'] do
  source 'generic.erb'
  mode 0644
  owner 'root'
  group 'root'
end

Please note that the test will still fail at this stage because we have not yet defined the variable attribute for node[‘nmdbase’][‘slogan’][‘path’]. In order to keep a strict separation between structure and data, we will need to add that information to an attributes file.

Step 3: Add Variables and Watch Test Pass

At this stage we need to provide a default value for the file location so we can test that the file creation process works while still providing each individual user of the slogan recipe the ability to override this default value. Both of these requirements are achieved by adding the following to attributes/default.rb


default['nmdbase']['slogan']['path'] = '/etc/slogan'

Now run the RSpec tests again. You should see the following at the completion of the output:


Finished in 3.6 seconds
28 examples, 0 failures

Step 4: Writing a Test for the Slogan Value

We’ve now verified the existence of the file. Now we need a test to validate its contents. Here we will need to modify the /spec/slogan_spec.rb generated in step #1. Add the following code after the previous assertion:


it 'Writes a slogan to the slogan file.' do
  expect(chef_run).to render_file('/etc/slogan')
    .with_content(/^Your Slogan$/)
end

Similar to step #1, running the RSpec tests at this stage will result in a failure in the output.

Step 5: Modify Recipes and Default Attributes

Now that the expected result has been defined, the next step is to modify the slogan recipe (created in step #2) so that it will populate the file with the desired slogan string. First, apply the following line to the template resource within recipe/slogan.rb:


variables(data: [node['nmdbase']['slogan']['value']])

Similar to step #3, we now need to add the following default value within the attributes file (attributes/default.rb) in order to match the expected result of the test in step #4:


default['nmdbase']['slogan']['value'] = 'Your Slogan'

Now if we re-run the RSpec tests, you’ll see the following summary in the log output:


Finished in 3.64 seconds
29 examples, 0 failures

Success! You’ve now gone through the complete process of

  • Evaluating a user story
  • Defining the acceptance criteria.
  • Creating a test to meet each criteria.
  • Creating a Chef recipe and default attributes to meet the requirements of each test.
  • Using RSpec to verify that each test now passes.

It’s important to underscore that this was all done prior to needing a VM, which means that you were able to completely test locally and efficiently. This also means that there is a significant amount of trust and confidence regarding any commits going upstream for additional levels of testing and integration.

What Can I Test?

Once you feel comfortable writing your first tests, the next step is understanding all of the various things that can be tested. The following is a short list of ideas to get started with:

  • Existence of particular file and/or directory.
  • Permissions of a particular file and/or directory.
  • Contents of a particular file.
  • The existence of particular services being available.
  • The configuration of server ports.
  • Anything that can be received as output from a bash script.

You can find all of these and more in the examples directory of the chefspec repo.

What should I test?

Behavior driven development is a little more specific than test driven development with respect to what should be tested. We will address this in more detail in subsequent articles in this series.

Next Steps

The next article in this series addresses Integration tests with kitchen.