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!