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 yourPATH
. - 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 Gemsetmy-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 Gemsetmy-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 Gemsetmy-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.
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.