Ruby Behavior Driven Development Tutorial

Exercise 1: Ruby Project Setup

by Richard Kuehnel


This tutorial requires the following RubyGems to be installed:

aruba
bson_ext
builder
bundler
cucumber
facets
gli
io-console
json
mongo
nori
rake
reek
rspec
settingslogic
simplecov
sinatra
webrick
xml-simple
yard

To list the versions of your installed gems, execute

gem list

(For testing this tutorial, the versions of the gems that I have installed are listed here. I am using ruby 2.0.0p647 (2015-08-18) [x86_64-linux].)

Exercise 1 covers the following topics.

  1. using Yard for automated documentation
  2. what belongs and does not belong in a project's README file
  3. setting up a project documentation server using WEBrick
  4. using GLI for command line applications
  5. configuring automated tests
  6. packaging a project as a RubyGem
  7. writing Rake tasks

Ruby Project Conventions

When you browse source code on GitHub you quickly get accustomed to the organizational layout of a typical Ruby application. A project's root directory has a Rakefile, a .gemspec, and a README.rdoc. Unit tests are in the test or spec subdirectory; production code is in lib; and executables are in bin. Innovation in file location is not appreciated by developers. They seek conformity, a project structure that blends with the crowd. This makes things easy to find.

In this exercise you will practice project setup. Your directory structure will be organized to make it easy for you to come back and pick up where you left off. Your file locations will make it simple for newcomers to come aboard and reuse your code.

The Project Directory Structure

From your working directory create a new RubyGem project to brew India Pale Ale:

bundle gem ipa

Specify "rspec" for tests. Take a look at each of the files that are generated. The directory structure and the scaffolding in ipa/ipa.gemspec are particularly helpful. If you are writing a simple Ruby code library (without a command line interface), a REST server, or something else fairly small, then bundle gem is a great way to start a project. For a new command line application, however, it is better to use the GLI RubyGem instead.

Remove the ipa directory and everything in it.

Use GLI (which stands for "Git-Like Interface") to create a command line application called StatOwl that will handle the commands mean, median, variance, and all.

gli init statowl mean median variance deviation all
cd statowl

The terminal output shows the new files and directories that were created. As you saw earlier, convention puts production code in the project's lib directory and executables in bin. Take a look at lib/statowl/version.rb. It keeps track of the current project version. In the future when you are ready to begin version 0.0.2 you will only have to change the version number here.

Usually there is only one file in the lib directory. Read that file now to find out why it exists. The rest of your production code will go into the lib/statowl directory.

Production Code Directories

The lib directory structure reflects the project's module hierarchy. For a large project with multiple modules and many classes like

ModuleA::ModuleA1::MyClass
ModuleA::ModuleA2::AnotherClass

the classes should be defined in

lib/statowl/module_a/module_a1/my_class.rb
lib/statowl/module_a/module_a2/another_class.rb

This is similar to Java, where the directory structure corresponds to the package hierarchy.

One exception to the module-directory paradigm applies to the project you are working on. You will be extending Ruby's Array class by adding methods that compute statistics. Computing the mean value of an array, for example, looks like the following.

class Array
  def mean
    reduce(:+) / size.to_f
  end
end

my_average = [1,2,3,4].mean     # -> 2.5

Since Array is not a new class defined for the Statowl module, it would be confusing to put it in a file called

lib/statowl/array.rb

so by convention, extensions to existing classes are put into extension files. Your project, for example, will extend Array in a file called

lib/statowl/extensions.rb

If you make a lot of modifications to many core classes (maybe an idea worth reconsidering...) you can create an entire directory for them called extensions, as in

lib/extensions/array.rb
lib/extensions/enumerable.rb

The bottom line: if you want to modify core Ruby, you should use extension files. This helps developers find modifications that might conflict with other code. You will draw their wrath if you randomly extend core classes somewhere else.

Documentation

A README file is important to introducing your project to new users. It contains everything needed to begin working on a project, but nothing more. This includes

  1. a brief description of the project's purpose
  2. examples of typical applications
  3. installation instructions

Remove the statowl.rdoc file. Read through the following RDoc text. Then replace the contents of README.rdoc with this text:

= StatOwl

Convenient ways to compute statistics for a bunch of numbers.

== Description

This project computes the mean, median, variance, and standard deviation
of numerical data. It also stores data in a MongoDB database for retention and future use.

StatOwl has three basic interfaces: a Ruby API that extends the
Array class, a command line application, and a Sinatra-based REST service.
The output format can be plain text, HTML, XML, or JSON.

== Synopsis

statowl [options] command

== Basic Command Line Usage

Compute the mean:

     $ echo '1,2,3,4' | statowl mean

Read input data from the console and output statistics in plain text to the console:

     $ echo '1,2,3,4' | statowl all

Format the output as HTML:

     $ echo '1,2,3,4' | statowl -f html all

== Installing the Gem

Simply type this at the command prompt:

     $ sudo gem install statowl

A README file does not completely document a project. It provides an overview and a quickstart to get code running. A README file that has too much detail can quickly become obsolete.

Read again the Description, Synopsis, and Basic Command Line Usage sections. This is a high-level overview of what you will be implementing in the exercises to come. You will start by extending the Array class to compute statistics, thereby creating a typical Ruby API library. Then you will wrap the API in a command line application, so that non-Rubyists can compute statistics from the command line, and a web service to provide statistics for web applications. Next you will write a Ruby API to enable both numbers and statistics to be stored and retrieved in a database. Then you will integrate it with your web services. This is difficult to justify for a statistics application, but important to round out these exercises, because a web service interface to a database is an essential element of create, read, update, and delete (CRUD) applications.

Read the following code that creates a WEBrick webserver. Then insert this code into a new file bin/webrick.

#!/usr/bin/env ruby

require 'webrick'

include WEBrick

puts "start"

dir = Dir::pwd + '/doc'
host = 'localhost'     # or the domain or IP address of your choice
port = 8080          # or another port of your choice
puts "serving up http://#{host}:#{port}/"

s = HTTPServer.new(
  :Port => port,
  :DocumentRoot => dir
)

trap("INT") { s.shutdown }
s.start

This enables you to view the project's documentation as it appears to users.

Make the file executable via

chmod +x bin/webrick

Execute this command to invoke Yard, a Ruby documentation tool:

yard doc

and then execute

bin/webrick

Once the WEBrick server has started up, navigate to http://localhost:8080/ (or your specified host and port) in your browser.

Where does the project description come from?
The information is from your README.rdoc file. Yard translates this into a web page at
doc/index.html.
  

Notice how your command line examples are automatically formatted to look like they were typed on a console. Yard marks them up as <pre class="code ruby"><code class="ruby">$ YOUR_COMMAND</code></pre> and adds some CSS to make this happen.

Using the Command Line App

Your project is ultimately designed to be executed as a user-installed RubyGem will all of its required gems specified in statowl.gemspec. As a developer, however, you need a way to run your executables directly from the working directory without having to completely install the gem. This is accomplished by invoking bundle exec. Try this:

bundle exec bin/statowl help

Ultimately users of your RubyGem will be able to execute statowl help directly, without bundle exec or the full path to the executable file. In development, however, it is common practice to use bundle exec. It is much faster than packaging and installing a gem every time you test new code.

Take a look at bin/statowl to observe the scaffolding that has been set up. GLI distinguishes between global flags and switches, which appear before the command, and command flags and switches, which appear after the command. This is the command line syntax:

statowl [global options] command [command options] [arguments...]

You will use global flags that apply to all commands. Global switches and command options will not be needed. Edit bin/statowl to remove unnecessary code and write appropriate comments where needed. The file should look like this:

#!/usr/bin/env ruby

require 'gli'
require 'statowl'

include GLI::App

program_desc 'A command line app that computes statistics'

version Statowl::VERSION

subcommand_option_handling :normal
arguments :strict

desc 'Describe some flag here'
default_value 'the default'
arg_name 'The name of the argument'
flag [:f,:flagname]

desc 'Computes the mean'
command :mean do |c|
  c.action do |global_options,options,args|
  end
end

desc 'Computes the median'
command :median do |c|
  c.action do |global_options,options,args|
  end
end

desc 'Computes the variance'
command :variance do |c|
  c.action do |global_options,options,args|
  end
end

desc 'Computes the standard deviation'
command :deviation do |c|
  c.action do |global_options,options,args|
  end
end

desc 'Computes all statistics'
command :all do |c|
  c.action do |global_options,options,args|
  end
end

exit run(ARGV)

Now try this:

bundle exec bin/statowl help

Notice how GLI still includes command options and arguments in the synopsis, even though you have not defined any. This has the potential to be confusing to users of your gem. Next, execute the following:

bundle exec bin/statowl help mean
bundle exec bin/statowl help deviation
bundle exec bin/statowl help all

If you were to deploy your project as a RubyGem in its current state, then users would be able to execute statowl help to get general help information and statowl help all to get help with the statowl all command. bundle exec and the path to the executable would not be required.

Setting Up Tests

So far your project has no functionality, other than generic help messages. Good practice dictates, however, that no new features be added before writing tests.

What happens if you execute the command rake without any arguments?

Ouch! Lots of errors! It is important to note, however, that the default setup for Rake tries to run tests by default. Testing is deeply ingrained in Ruby culture. Take a look at the extensive unit tests in test/default_test.rb. Then remove the entire test subdirectory. Instead of running Ruby unit tests you will use RSpec and Cucumber. RSpec is a popular library for Test Driven Development. Cucumber is an Acceptance Testing environment that runs on RSpec.

You will use Yard to generate RDocs, not the existing RDoc task specified in Rakefile.

Edit your Rakefile to delete Rake::RDocTask and Rake::TestTask. Then set up tasks for verbose Cucumber and RSpec tests. The file should now look like this:

require 'rake/clean'
require 'rubygems'
require 'rubygems/package_task'
require 'cucumber'
require 'cucumber/rake/task'
require 'rspec/core/rake_task'
require 'yard'

spec = eval(File.read('statowl.gemspec'))

Gem::PackageTask.new(spec) do |pkg|
end

desc 'Run RSpecs'
RSpec::Core::RakeTask.new do |t|
  t.rspec_opts = ["--color", "--format", "documentation"]
end

CUKE_RESULTS = 'doc/cuke_results.html'
CLEAN << CUKE_RESULTS

desc 'Run features'
Cucumber::Rake::Task.new(:features) do |t|
  opts = "features --format html -o #{CUKE_RESULTS} --format pretty -x"
  opts += " --tags #{ENV['TAGS']}" if ENV['TAGS']
  t.cucumber_opts =  opts
  t.fork = false
end

YARD::Rake::YardocTask.new do |t|
end

task :default => :features

Execute the command rake --tasks to see the tasks that you have at your disposal.

Remove the file features/statowl.feature. You will be writing your own features in a later exercise. Execute rake to observe that you are testing Cucumber scenarios by default.

Packaging Your Gem

It is good practice to ensure that your project can be packaged and deployed as a RubyGem from the very beginning. Moreover, periodically verifying that you have a working gem can help identify deployment issues early, particularly problems with file paths.

Packaging is controlled by the Gem::Specification class, which by convention is in a file in your project root directory with a .gemspec extension. Your gemspec file, for example, is statowl.gemspec.

Read through the following configuration for packaging your project as a RubyGem.

require File.join([File.dirname(__FILE__),'lib','statowl','version.rb'])
spec = Gem::Specification.new do |s|
  s.name = 'statowl'
  s.version = Statowl::VERSION
  s.description = 'convenient ways to compute statistics'
  s.authors = ['YOUR NAME']
  s.platform = Gem::Platform::RUBY
  s.summary = 'Statistics utilities and services'
  s.files = Dir["lib/**/*"]
  s.require_paths << 'lib'
  s.has_rdoc = true
  s.extra_rdoc_files = ['README.rdoc']
  s.rdoc_options << '--title' << 'statowl' << '--main' << 'README.rdoc' << '--ri'
  s.bindir = 'bin'
  s.executables << 'statowl'
  s.add_development_dependency('rake')
  s.add_development_dependency('cucumber')
  s.add_development_dependency('aruba')
  s.add_runtime_dependency('gli')
end

Use the code to replace the contents of statowl.gemspec. Then execute the following:

rake clobber
rake features
rake package
gem install pkg/statowl-0.0.1.gem

Execute gem list and look for your statowl gem.

You should now be able to execute statowl help using the installed gem, just like an ordinary user. The most common problem at this point is failure to package all the necessary files. This line in the statowl.gemspec file includes every file in the lib subdirectory:

s.files = Dir["lib/**/*"]

Its success can be verified using the command

gem contents --no-prefix statowl

You don't want the installed gem to interfere with the one you are developing, so uninstall it in preparation for the next exercise. Answer "Y" to the prompt.

gem uninstall statowl

Exercise Summary

In this exercise you learned about Ruby conventions for organizing a project. You also learned how files and directories relate to Ruby modules and classes. The class Statowl::Utilities::Logger, for example, should be defined in the lib/statowl/utilities/logger.rb file.

You configured Yard for automated code documentation and setup WEBrick to verify the resulting RDocs. Yard has many advanced features that were not covered during this exercise but are worth exploring as your projects grow in popularity. It can be configured for automatic updating, for example, and is easy to customize and extend.

You learned what belongs in a README file and Ruby's convention of keeping it short. You used the GLI gem to create scaffolding for a command line application. For simple applications you could use bundle gem my_killer_app to create a new project directory structure.

Next you learned how to write new Rake tasks. Rake, in essence, is Ruby Make. It uses Rakefiles in the same way that Make uses Makefiles.

You also tested the packaging of your new RubyGem.

In the next exercise you will use Behavior Driven Development to write production code.