what we blog

TDD with Guard

Using Guard for TDD

In the Ruby community testing and in particular test-driven development (TDD) is a common practice. When writing a gem this is often one of the first steps to set up by picking a suitable test framework. Creating a gem via Bundler also automates this step and gets one started immediately.

The most important aspect of TDD is the fast feedback cycle of writing code and running tests. This practice is best expressed by the red-green-refactor cycle. In this workflow first a failing test is written (red). Then the developer writes the code to make the test pass, often as minimal as possible (green). The last step is to refactor the code to integrate it in a suitable way (refactor). Tests need to run often and in a fast manner. A cycle then takes a few seconds or minutes rather than hours to accomplish.

When writing a Ruby gem a few tools are at hand to make TDD a very painless practice. This article describes a setup with Guard and RSpec to allow a development environment with a very short feedback cycle between writing code and running tests. Furthermore we integrate Rubocop to validate the code style of our written code.

Gem setup

First we make sure the gem project is set up correctly for our purposes. Most gems follow the same folder structure and have files in a similar place. To start from scratch it is best to follow one of the many avaialble guides, for example

To get things started quickly, first install bundler (if not already available).

$ gem install bundler

Bundler can be used to create a gem project for us. The command

$ bundle gem arithmetic

will set up the folder structure for a gem named arithmetic and provides a good starting template.

The test framework for the example is RSpec, but using a different one, e.g. minitest, should not be a problem, the same principles apply. First we add the necessary gems to the Gemfile (or alternatively to the gemspec).

# Gemfile
group :test, :development do
  gem 'guard'
  gem 'rspec'
  gem 'rubocop'
  gem 'guard-bundler', require: false
  gem 'guard-rspec', require: false
  gem 'guard-rubocop', require: false
end

The gems are added to the groups test and development, they are not necessary for the final gem release and are only used during development. The project structure then looks similar to:

.
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── arithmetic.gemspec
├── lib
│   ├── arithmetic
│   │   └── version.rb
│   └── arithmetic.rb
└── spec
    ├── lib
    │   └── arithmetic_spec.rb
    └── spec_helper.rb

Note the associated arithmetic_spec.rb file is located under spec/lib. It's more a matter of taste, but this allows us to have other folders in spec/ as well, e.g. to have separate folders for integration & acceptance tests which can take exceedingly long to execute. The tests in spec/lib should be unit tests to have them run often and quickly. Otherwise testing becomes cumbersome in the long run and can easily lead to a situation where the TDD approach is abandoned.

With this setup in place let's see how to set up Guard.

Guard

Guard is a Ruby tool for executing tasks automatically when files or directories are modified. Similar to Rake's Rakefile, a Guardfile can be used to define these tasks that are triggered by specific rules. Guard comes with a CLI tool that can be executed on the command line. By default it looks for the Guardfile in the same directory.

A simple Guardfile might look as follows:

# Guardfile
guard :bundler do
  watch('Gemfile')
end

The above code defines a Guard plugin named bundler, that runs the bundler command whenever the Gemfile changes. The result is, if the Gemfile is modified, e.g. when saved, all gems are updated. One important aspect of Guard is that all files and folders relative to the working directory are watched for modifications, but only for the specified patterns (strings or regular expressions) defined by the watch command the given task is executed.

If there is no Guardfile available yet one can generate this file by running

$ guard init

in the project folder. This creates a Guardfile with a few comments. There is a Ruby gem for RSpec that works as a Guard plugin, called guard-rspec. We already added this gem to the Gemfile above.

Most plugins come with an initializer that can be run to add an appropriate task to the Guardfile. For RSpec run:

$ guard init rspec

For a standard Ruby gem project with RSpec the following Guard definition is of interest.

# Guardfile
guard :rspec, cmd: 'rspec' do
  watch(%r{^spec/.+_spec\.rb$})
  watch(%r{^lib/(.+)\.rb$})     { |m| "spec/lib/#{m[1]}_spec.rb" }
  watch('spec/spec_helper.rb')  { "spec/lib" }
end

There might be other watch commands present, e.g. for a Rails project. The given definition above checks for modifications of all files in the folder spec/ ending with the suffix *_spec.rb. A convention is to name the associated RSpec file after the file under test by using the suffix _spec. The second watch command above associates every file of the lib folder with the matching spec file in the spec/lib folder. So whenever the code in the lib folder changes the associated spec file is run with RSpec. For example a file lib/sample.rb is then matched with spec/lib/sample_spec.rb. Modification of a source file then only runs the tests from the associated spec file, keeping a good focus on only the things that are of interest. The last watch command instructs RSpec to re-run the specs of the whole spec/lib folder when the spec_helper.rb file is modified. The spec_helper.rb is used to set up the test environment, e.g. to configure RSpec, to import libs or define helper functions. Guard also allows a list of arguments to get forwarded to the rspec command. For example to change the output format:

# Guardfile
guard :rspec, cmd: 'rspec -c -fp' do
  # [...]
end

To begin using Guard, simply run guard on the command line from the project folder. The cmd argument is necessary in recent versions of the guard-rspec plugin and specifies how RSpec is executed. Now we are able to start writing code and have the tests run immediately when the code or any of the associated spec files are modified.

Rubocop

Another useful tool in a Ruby gem project is Rubocop, a static code analyzer for checking Ruby code against a set of style rules. It is based on the community Ruby style guide. A coding style guide is a set of rules agreed upon by developers to ensure good code readability and maintainability. It is also a highly debated topic among developers. We assume that everyone agrees on a style guide and there is set of files which Rubocop can use to validate the code. Rubocop then can list all violations with appropriate messages.

Rubocop can be run on the command line by running rubocop. It scans the folder for Ruby files (ending with *.rb) and lists all style violations. Rubocop can be controlled by a .rubocop.yml where rules can be enabled / disabled, filters can be set, e.g. to include Rakefile or filter *.gemspec. For some rules the level of severity can be given.

Similar to RSpec, there is a guard plugin for Rubocop, the guard-rubocop gem, which also comes with an initializer.

$ guard init rubocop

This adds the following task to the Guardfile.

# Guardfile
guard :rubocop do
  watch(%r{^spec/.+_spec\.rb$})
  watch(%r{^lib/(.+)\.rb$})
end

Running Guard now activates both RSpec & Rubocop and provides an extensive output. Unfortunately the number of specs might grow too big and the list of Rubocop errors could become quite long.

To improve readability of the Guard output both plugins can take a list of input arguments to tweak the output. During development we are more interested in the current context and want to see the error message of the file under test. Guard allows to set a parameter named cmd, were the command line arguments to RSpec can be given. In order to show less output, but still provide enough info on a failed test we add cmd: 'rspec -c -fp' to make the output more concise. The argument all_on_start can also be given to run all tests initially when Guard starts.

Rubocop also accepts a list of command line arguments that format the output (run rubocop -h to show help). To select a different output format and to fail immediately on the first style error we add cmd: 'rubocop --format fuubar -F -D' to the Rubocop guard (see documentation).

One other thing we want to take care of is, whenever one of the tests fails we'd like to skip the Rubocop validation to stay in the context of the test. Guard has support for groups where similar tasks can be bundled into. A common use case of groups is to split the development into frontend and backend parts, for example in a bigger Rails project. A group accepts the argument halt_on_fail that when true will stop the execution of other tasks as soon as an error occurred. To run the new group from the command line the group can be passed via an argument: guard -g <group>. Alternatively this group can be defined via the scope keyword in the Guardfile as the default group. Running guard on the command line without any arguments then will run the default group.

Everything put together our final Guardfile looks as follows:

# Guardfile
scope group: :specs

group 'specs', halt_on_fail: true do
  guard :bundler do
    watch('Gemfile')
  end

  guard :rspec, all_on_start: true, cmd: 'rspec -c -fp' do
    watch(%r{^spec/.+_spec\.rb$})
    watch(%r{^lib/(.+)\.rb$})     { |m| "spec/lib/#{m[1]}_spec.rb" }
    watch('spec/spec_helper.rb')  { "spec/lib" }
  end

  guard :rubocop, all_on_start: false, cmd: 'rubocop --format fuubar -F -D' do
    watch(%r{^spec/.+_spec\.rb$})
    watch(%r{^lib/(.+)\.rb$})
  end
end

There are also a few options that Guard supports which are more likely user specific. For example the option clearing can be set to :on to clear the terminal window before showing the result of RSpec and Rubocop.

The developers of Guard realized that these types of options are personal preferences and might also change on different platforms, e.g. the notification mechanism on each operating system behaves differently. Therefore Guard also looks in the home folder for either a Guardfile or a .guard.rb file. Another feature of Guard is to send notifications, convenient for having a small popup notification or a sound to indicate either success or failure. These options are good candidates to put into the personal .guard.rb file as well. An example for this could be:

# ~/.guard.rb
notification :growl   # on OSX with growl notifications installed
clearing :on          # clears terminal

This makes it very easy to have general guards set for the current project and all personal preferences are included from a local configuration.

Conclusion

With this setup in place TDD can be done very easily. The main advantage of having this kind of setup is the developer does not need to leave the editor. A fast feedback loop is established where it's easy to see if tests are passing or if something breaks without the need to switch contexts. Once all tests pass the focus then shifts to the code style, validated by Rubocop. The feedback cycle is preferrably less than a second to iterate often and quickly.

The setup takes a bit to get used to, once in place it is something that might improve doing the practice of TDD a lot. A similar setup with Guard can also be used when developing in other programming languages, for example running a well prepared C++ project in combination with the test framework Google Test.

Happy TDD!