Introduction

Good practice is to run rubocop, foodcritic, and ChefSpec (rspec) tests, and test kitchen before pushing your changes, as we’ve talked about before.

And it’s really not THAT much to type every time to go:

$ rubocop
$ rubocop -D
$ rubocop -a
$ foodcritic
$ foodcritic . # I always forget the dot...
$ rspec --color -f d
$ kitchen test

But wouldn’t it be nice to automate this, so we just have one command to type?

Actually, you can now do that with delivery locally. But that comes with some other considerations which deserves its own post.

So for now, enter, Ruby’s Make! aka rake

Rakefile

Rake really is a very powerful tool. I recommend reading over the documentation linked as well as this article. We’ll focus on just enough rake to automate our tests.

Basic Rakefile

task default: :reticulate_splines

desc 'Reticulate splines'
task :reticulate_splines do
  puts 'Reticulating Splines...'
  # doing something useful like reticulating splines...
end

Then we can run it. Also we can run rake with the -T flag to see all tasks available.

$ rake -T
rake reticulate_splines  # Reticulate splines
$ rake
Reticulating Splines...

This isn’t very useful, but is a good structure to show the basics of a Rakefile.

Rakefile explanation

task default: :reticulate_splines

The first line describes the default task. This is the task that will be run when rake is called without any arguments.

desc 'Reticulate splines'

The next line is the description of the task. The desc function call must precede a task block.

task :reticulate_splines do
  puts 'Reticulating Splines...'
  # doing something useful like reticulating splines...
end

Finally, the task block. The task is named as a symbol, then within the block a function is performed. In this case we’re only printing a message, but it’s just Ruby, so we can do any useful functions.

Chef Rakefile

What are the things we always do?

To that end, let’s get started!

Rake Rubocop

The first thing to do, include RuboCop! Since version 0.10 RuboCop comes with a rake task.

require 'rubocop/rake_task'

RuboCop::RakeTask.new do |rubocop|
  rubocop.options = ['--display-cop-names']
end

First we require the library with the rake task.

Then we initialize the task, by calling RuboCop::RakeTask.new.

Personally, I like to have the cop names displayed when I run rubocop, so we take the opportunity to add that option when we run rubocop. You can pass any flags here you would pass on the command line.

Then when we run rake -T we get:

$ rake -T
rake rubocop               # Run RuboCop
rake rubocop:auto_correct  # Auto-correct RuboCop offenses

Now we can have rake run rubocop for us!

Rake Foodcritic

Similar to RuboCop, FoodCritic has built in rake tasks.

require 'foodcritic'

FoodCritic::Rake::LintTask.new do |foodcritic|
  foodcritic.options[:fail_tags] = %w(any)
end

First we again require the library. Then we initialize the FoodCritic tasks. Additional options can be set as keys:value pairs to #options.

Now we should have:

$ rake -T
rake foodcritic            # Lint Chef cookbooks
rake rubocop               # Run RuboCop
rake rubocop:auto_correct  # Auto-correct RuboCop offenses

Rake RSpec

Finally, RSpec tests. This also has a rake task. So following the same pattern…

require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new do |rspec|
  rspec.rspec_opts = "--color --format documentation"
end

And now we should have one more rake task:

$ rake -T
rake foodcritic            # Lint Chef cookbooks
rake rubocop               # Run RuboCop
rake rubocop:auto_correct  # Auto-correct RuboCop offenses
rake spec                  # Run RSpec code examples

Rake Test Kitchen

Finally Test Kitchen. And we follow the same pattern.

require 'kitchen/rake_tasks'
Kitchen::RakeTasks.new

We don’t really have any options to change, so we just call “new”. Now we have something along the lines of:

$ rake -T
rake foodcritic                   # Lint Chef cookbooks
rake kitchen:all                  # Run all test instances
rake kitchen:default-centos-7     # Run default-centos-7 test instance
rake kitchen:default-ubuntu-1604  # Run default-ubuntu-1604 test instance
rake rubocop                      # Run RuboCop
rake rubocop:auto_correct         # Auto-correct RuboCop offenses
rake spec                         # Run RSpec code examples

Pulling it all together

Remember I said I was lazy? Probably too lazy to say it. It’s great that we have one command to run everything, but I still have to remember and give rake a subcommand… if only there was a way that we could have one rake task call another…? Also note, we haven’t declared a default task yet.

task default: :all

desc 'Run all tests'
task all: [:test, :kitchen:all]

desc 'Run Rubocop and Foodcritic style checks'
task style: [:rubocop, :foodcritic]

desc 'Run all style checks and unit tests'
task test: [:style, :spec]

This gives us a few new commands that will call the various linters and test tools for us directly. You should have something like:

$ rake -T
rake all                          # Run all tests
rake foodcritic                   # Lint Chef cookbooks
rake kitchen:all                  # Run all test instances
rake kitchen:default-centos-7     # Run default-centos-7 test instance
rake kitchen:default-ubuntu-1604  # Run default-ubuntu-1604 test instance
rake rubocop                      # Run RuboCop
rake rubocop:auto_correct         # Auto-correct RuboCop offenses
rake spec                         # Run RSpec code examples
rake style                        # Run Rubocop and Foodcritic style checks
rake test                         # Run all style checks and unit tests

Final Form

Conclusion

Hopefully this will encourage you to run your tests before you push your code!

  • Q