What Is It?

rdp edited this page Sep 14, 2010 · 11 revisions

Ruby DocTest allows you to paste irb style examples as tests right in with your Ruby code. It also allows you to put tests in a separate documentation file and have the tests run automatically.

See the Example Usage page for a peek at some code.

Use Your “irb” Sessions as Tests

One of the most compelling features of Ruby DocTest over other test suites is the ability to bring Interactive Ruby (irb) sessions into your code via copy/paste and to use those sessions as your tests. This makes it far easier to create tests, since you just have to copy working examples from the console to within a comment in your code.

Your (easily created) tests thus double as documentation and tests for your new methods.

Let’s go through an example of this workflow.

Example Workflow

Let’s say we want to create a Digital Object Identifier library that will give us proper bibliographic references, and vice versa from doi.org. In fact, let’s say we are re-creating Rich Apodaca’s 22-line DOI library (note that his page is prettier, since it has source code highlighting):

require 'rubygems'
require 'hpricot'
require 'open-uri'

module DOI
  # Convert a doi into a bibliographic reference.
  def biblio_for doi
    doc = Hpricot(open("http://www.crossref.org/openurl/?" +
      "id=doi:#{doi}&noredirect=true&" +
      "pid=ourl_sample:sample&format=unixref"))

    journal = (doc/"abbrev_title").inner_html
    year = (doc/"journal_issue/publication_date/year").inner_html
    volume = (doc/"journal_issue/journal_volume/volume").inner_html
    number = (doc/"journal_issue/issue").inner_html
    first_page = (doc/"pages/first_page").inner_html
    last_page = (doc/"pages/last_page").inner_html
    "#{journal} #{year}, #{volume}(#{number}) #{first_page}-#{last_page}"
  end

  # Convert a bibliographic reference into a DOI.
  def doi_for journal, year, volume, issue, page
    doc = Hpricot(open("http://www.crossref.org/openurl/?" +
      "title=#{journal.gsub(/ /, '%20')}&volume=#{volume}&" +
      "issue=#{issue}&spage=#{page}&date=#{year}&" +
      "pid=ourl_sample:sample&redirect=false&format=unixref"))
   (doc/"doi").inner_html
  end
end

After showing his code, Rich tests that it is doing what it is advertised to do, by using an irb session. Super! Let’s do that too:

>> require 'doi.rb'
>> include DOI
=> Object
>> biblio_for "10.1021/cr00032a009"
=> "Chem. Rev. 1994, 94(8) 2483-2547"
>> doi_for "Chem. Rev.", 1994, 94, 8, 2483
=> "10.1021/cr00032a009"

Now, let’s copy-paste our irb session into the code:

require 'rubygems'
require 'hpricot'
require 'open-uri'

=begin
doctest: Digital Object Identifier module can be loaded
>> include DOI
=> Object
=end
module DOI
  # Convert a doi into a bibliographic reference.
  #
  # doctest: Try loading a sample DOI and get its bibliographic reference
  # >> biblio_for "10.1021/cr00032a009"
  # => "Chem. Rev. 1994, 94(8) 2483-2547"
  def biblio_for doi
    doc = Hpricot(open("http://www.crossref.org/openurl/?" +
      "id=doi:#{doi}&noredirect=true&" +
      "pid=ourl_sample:sample&format=unixref"))

    journal = (doc/"abbrev_title").inner_html
    year = (doc/"journal_issue/publication_date/year").inner_html
    volume = (doc/"journal_issue/journal_volume/volume").inner_html
    number = (doc/"journal_issue/issue").inner_html
    first_page = (doc/"pages/first_page").inner_html
    last_page = (doc/"pages/last_page").inner_html
    "#{journal} #{year}, #{volume}(#{number}) #{first_page}-#{last_page}"
  end

  # Convert a bibliographic reference into a DOI.
  #
  # doctest: Try loading a sample bibliographic reference and get its DOI
  # >> doi_for "Chem. Rev.", 1994, 94, 8, 2483
  # => "10"
  # Convert a bibliographic reference into a DOI.
  def doi_for journal, year, volume, issue, page
    doc = Hpricot(open("http://www.crossref.org/openurl/?" +
      "title=#{journal.gsub(/ /, '%20')}&volume=#{volume}&" +
      "issue=#{issue}&spage=#{page}&date=#{year}&" +
      "pid=ourl_sample:sample&redirect=false&format=unixref"))
   (doc/"doi").inner_html
  end
end

Let’s run that test code now:

$ rubydoctest doi.rb

=== Testing 'doi.rb'...
 OK  | Digital Object Identifier module can be loaded
 OK  | Try loading a sample DOI and get its bibliographic reference
FAIL | Try loading a sample bibliographic reference and get its DOI
       Got: "10.1021/cr00032a009"
       Expected: "10"
         from doi.rb:33
3 comparisons | 3 doctests | 1 failures | 0 errors

Uh-oh! I didn’t copy the result correctly. After fixing the string result, we get:

=== Testing 'doi.rb'...
 OK  | Digital Object Identifier module can be loaded
 OK  | Try loading a sample DOI and get its bibliographic reference
 OK  | Try loading a sample bibliographic reference and get its DOI
3 comparisons | 3 doctests | 0 failures | 0 errors

Done!

Tips and Tricks

One nice feature is the !!! directive, first introduced by Tom Locke in version 0.2 of Ruby DocTest. This lets you open an irb session at any point during your documentation, in the context of the Ruby DocTest code you’ve already written!

For example, let’s say you want to play with some objects after instantiating a complex set of interdependencies. Ok, maybe this won’t be a complex set, but you can use your imagination:

# >> goals = ["mind", "body", "relationships"]
# >> goals.map{ |g| g.capitalize }
# !!!

Now run the rubydoctest, and at the !!! point, an irb session will open where you can play with the “goals” variable, in context.