Making Rails and TeamCity Play Well Together

Continuous integration has long been established as a valuable practice, but getting all the pieces together can be a pain. Matt Fletcher shared some of his difficulties in getting things setup satisfactorally for Rails. After much experimentation, I’m actually happy with how my Rails project is being tested with TeamCity. This might not be the perfect setup, but hopefully my experience can be useful.

Setup

Here’s how I’ve got things setup:

The Platform

The project is Rails 3.1, Ruby 1.9.3 (via RVM), and uses Bundler.

The Tests

Tests are written with RSpec and Steak. Acceptance tests are performed through Capybara-Webkit with the headless gem.

Originally I was using Selenium, but for CI, I needed headless operation. A word of warning: Selenium seems to be much less timing-sensitive than headless Capybara-webkit. I had to introduce a lot of wait_until or Firepoll calls, but other than that it was fairly smooth sailing.

The Build Agent Machine

The majority of Atomic Object’s build machines are running Ubuntu Linux. They needed a couple new packages:

  • Install RVM and create a gemset if necessary. As soon as you get more than one project, it’ll be very valuable to let each run on its intended Ruby version. You don’t need to source the environment (TeamCity has some support for running Ruby and Rake processes inside RVM‘s environment).
  • Make sure you have $HOME/.rvm/bin in your PATH.
  • Install Xvfb – Capybara-webkit is window-less, but it still requires an X installation to function. The X Virtual Frame Buffer server combined with the Headless rubygem should get you going.

Build Steps

In Step 3 of TeamCity’s build configuration, I created 4 build steps for my project:

1. Bundle

The first step is to use Bundler to retrieve your gem dependencies:

  • Runner type: Command Line
  • Run: Custom script
  • Custom script: rvm 1.9.3-p125 do bundle install

This command is why you need RVM in your path.

2. Create Your Database

TeamCity’s Rake runner will do some work for you to get you a test database, but it won’t bootstrap it. By forcing a clean database each time you can have different build configurations at different stages of development (such as having a bleeding edge configuration but also maintaining a production maintenance branch.)

  • Runner type: Rake
  • Rake tasks: db:drop db:create db:schema:load
  • RVM interpreter: ruby-1.9.3-p125 and Gemset my-gemset
  • Bundler: true
  • Attached reporters: deselect all

Note that we do not specify that this should run in the test environment. This loads the correct database schema for the current build into the development database, and when the tests run later in the process, they’ll trigger db:test:prepare.

3. Build Assets

On my development machine, I’m almost never going to compile my assets or test them out. I should, but the time required means I’ll usually skip it. However we can configure our project to use precompiled assets in acceptance tests in the automated builds. This can catch errors such as not adding an asset to the list of precompiled files in config/application.rb.

This build step looks basically like the previous:

  • Runner type: Rake
  • Rake tasks: assets:clean assets:precompile
  • RVM interpreter: ruby-1.9.3-p125 and Gemset my-gemset
  • Bundler: true
  • Attached reporters: deselect all

4. Run the Tests

The last step is to actually run rake.

  • Runner type: Rake
  • Rake tasks: leave blank
  • RVM interpreter: ruby-1.9.3-p125 and Gemset my-gemset
  • Bundler: true
  • Attached reporters: Select appropriate reporter for your project

Build Failure Conditions

I ran into an error where my TeamCity would (for some reason) fail to correctly inject its RSpec reporter into the Ruby process. I’d see the string “LoadError: cannot load such file — teamcity/spec/runner/formatter/teamcity/formatter”. When that happened, TeamCity wouldn’t run any tests and would report success.

We can protect against this in Step 4 of TeamCity’s configuration. Add a new build failure condition:

  • Fail build if build log contains exact text
  • Enter the string LoadError: cannot load such file -- teamcity/spec/runner/formatter/teamcity/formatter

Environment Variables

We need to let our Ruby code know that we’re in CI so we can do some things differently (such as the precompiled assets). On Step 7 in TeamCity add an environment variable parameter of CI with the value true.

Application Updates

Spec Helper

In order to get Capybara Webkit working on Linux, I needed to use the Headless rubygem. I didn’t run into any issues on OSX, but I added the below to my spec/spec_helper.rb to initialize Xvfb on the necessary platforms.

config.before(:suite) do
unless RUBY_PLATFORM[/darwin/] headless = Headless.new headless.start end end

The RUBY_PLATFORM variable might not be the best way to check operating system, but it is probably sufficient for test code run only at your shop under controlled circumstances.

Assets

As mentioned above, it is valuable to have your test environment closely match production. I added the below to the bottom of my config/environments/test.rb:

if ENV["CI"] == "true"
  config.assets.compile = false
  config.assets.digest = true
end

Run it

That was all I needed to do to get a reliable, comprehensive test environment running in TeamCity.

Conversation
  • Vladislav Rassokhin says:

    Hi Mike,
    Could you please describe problem with TeamCity RSpec tests reporter in our tracker? Build logs may be very helpful.

    Also you can use Ruby Environment Configurator build feature to easy maintain RVM settings for all build steps at once.

  • Comments are closed.