Los Techies : Blogs about software and anything tech!

Querying Rally with ruby


I've had the opportunity to play around a bit with the Rally REST API to automatically generate acceptance test stubs at work recently. For those of you who don't know, Rally is a bug/feature tracking software that allows you to write stories and test cases, along with tracking hours spent implementing all of these. Usually, a product manager will create stories ahead of time and developers then bring the story to life, after which acceptance tests have to be written and passed. 

So we decided to generate empty test stubs for each and every test case that didn't have a test already, which will save a ton of time to all the developers. Moreover, since Rally is integrated to TeamCity, we would know instantly which tests have been implemented, and which ones haven't, the moment we kick off our acceptance test job. 

Should you want to do something similar, or simply query Rally, I hope the following code will help you with that.

First things first, though. You will need to install the ruby gem rally_rest_api (here's the documentation). In a command prompt, type in:

gem install rally_rest_api

Then, start up your favorite ruby editor and start coding! The first thing we did was to put a wrapper class around the gem's RallyRestAPI class:

   1:  class MyRally
   2:    def initialize
   3:      @rally = RallyRestAPI.new(:base_url => "https://rally1.rallydev.com/slm",
   4:                                :username => "your_username",
   5:                                :password => "your_password")
   6:    end
   7:   
   8:    def get_stories(project_name)
   9:      query_results = @rally.find(:project) {equal :name, project_name}
  10:      project = query_results.results.first
  11:      
  12:      query_results = @rally.find(:hierarchical_requirement, :fetch => true) {equal :project, project}
  13:      query_results.map do |story_result| 
  14:        puts "fetching story #{story_result.formatted_i_d}"
  15:        Story.new(story_result, self)
  16:      end
  17:    end
  18:   
  19:    def get_test_cases_for_story(story_result)
  20:      test_case_query_result = @rally.find(:test_case, :fetch => true) {equal :work_product, story_result}
  21:      test_case_query_result.map do |test_case_result|
  22:        puts "--fetching test case #{test_case_result.formatted_i_d}"
  23:        TestCase.new(test_case_result)
  24:      end
  25:    end
  26:  end


In the initialize method, the RallyRestAPI object is instantiated with your credentials, which will allow it to make the proper service calls to the Rally server. Then, the get_stories method takes a project name as a parameter and will find all the stories belonging to that project in Rally. 

Note that the @rally.find call on line 9 takes both parameters and an optional block. If you wanted to, you could yield to the caller inside the block instead of having to pass in the project name to the get_stories method. That would allow you to specify any extra constraints at the moment you call get_stories. For example, instead of writing:

   1:  my_rally.get_stories("Sharks with laser beams")

you could write:

   1:  my_rally.get_stories do
   2:    equals :name, "Sharks with laser beams"
   3:    greater_than :priority, "Fix Immediately"
   4:  end

which allows you to be more specific in your querying without having to create a new method for each and every single combination of constraints you need to query your stories.

Back up in the code, the first thing the script does is to find the actual project. Then, it uses the project to find all the user stories that have been defined for it. The result set that is returned by the Rally API is mapped to an array of Story object, which have been implemented as a wrapper around the returned data. Finally, the test cases are fetched in the same manner, for each story, and mapped to a TestCase object. 

Here is the code for the Story and TestCase classes:

   1:  class Story
   2:    attr_reader :formatted_name
   3:   
   4:    def initialize(story_result, rally)
   5:      @formatted_name = "#{story_result.formatted_i_d[5..-1]}_#{story_result.name.underscorize}"
   6:      @test_cases = rally.get_test_cases_for_story(story_result)
   7:    end
   8:   
   9:    def to_s
  10:      str = @formatted_name + "\n"
  11:   
  12:      @test_cases.each do |test_case|
  13:        str += "  name: #{test_case.name}\n"
  14:        str += "  id: #{test_case.id}\n"
  15:        str += "  pre_conditions: #{test_case.pre_conditions}\n"
  16:   
  17:        if test_case.steps != nil
  18:          test_case.steps.each { |step| str += "    - #{step}\n"}
  19:        end
  20:        
  21:        str += "\n"
  22:      end
  23:   
  24:      str += "--------------------------------\n"
  25:    end
  26:  end

   1:  class TestCase
   2:    attr_reader :name, :id, :pre_conditions, :steps
   3:    
   4:    def initialize(test_case_result)
   5:      @name = test_case_result.name.underscorize
   6:      @id = test_case_result.formatted_i_d[2..-1]
   7:   
   8:      pre_conditions = test_case_result.pre_conditions
   9:      @pre_conditions = test_case_result.pre_conditions.clean_up unless pre_conditions == nil
  10:   
  11:      @steps = test_case_result.validation_input.clean_up unless test_case_result.validation_input == nil
  12:    end
  13:  end

The astute reader (I've always wanted to say that!) will have noticed that we use obscure methods like underscorize on the story and test case names. These have been added to the String class for our own particular reasons which, in this case, require all the words in the names to be separated with underscores.

That's pretty much as far as we went with the API so far, and I hope it'll help any of you who use Rally as well! Have a great weekend!

Kick It on DotNetKicks.com
Posted Sep 25 2009, 06:03 PM by Louis Salin

Comments

Enrique Sanchez wrote re: Querying Rally with ruby
on 01-14-2010 6:24 PM

Mmm what was the change on the String class? i mean the underscorize part?

Add a Comment

(required)  
(optional)
(required)  
Remember Me?

Enter the numbers above:
Copyright Los Techies 2008, 2009. All rights reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems