くりにっき

フルスタックキュアエンジニアです

gemの複数バージョンカジュアルテスト #shibuyarb

gemを作っていると複数rubyのバージョンや依存gemのバージョンをカジュアルに組み合わせてテストをしたいというのがよくあると思いますが、あまりやり方が知られていない気がするのでまとめてみます

今回のエントリのサンプルプロジェクト

github.com

渋谷.rb[:20150520] のLT資料

最初にまとめ

Travis CI使うのがめっちゃ楽。

セットアップ

bundle gem コマンドでテストを生成

$ bundle gem multiple_version_test_sample -t --mit
Creating gem 'multiple_version_test_sample'...
      create  multiple_version_test_sample/Gemfile
      create  multiple_version_test_sample/.gitignore
      create  multiple_version_test_sample/lib/multiple_version_test_sample.rb
      create  multiple_version_test_sample/lib/multiple_version_test_sample/version.rb
      create  multiple_version_test_sample/multiple_version_test_sample.gemspec
      create  multiple_version_test_sample/Rakefile
      create  multiple_version_test_sample/README.md
      create  multiple_version_test_sample/bin/console
      create  multiple_version_test_sample/bin/setup
      create  multiple_version_test_sample/LICENSE.txt
      create  multiple_version_test_sample/.travis.yml
      create  multiple_version_test_sample/.rspec
      create  multiple_version_test_sample/spec/spec_helper.rb
      create  multiple_version_test_sample/spec/multiple_version_test_sample_spec.rb

オプション解説

  • -t = テストディレクトリを作成
  • --mit = MITのLICENSE.txtを作成
    • 昔はデフォルトで作ってくれてたんだけどいつの間にか作られなくなってた。。。

Travis CIを使う

https://travis-ci.org/

CI系のサービスは CircleCIwercker などがあるけど、複数Rubyのバージョンやgemのバージョンをカジュアルに組み合わせてテストできるのはTravis CIの強み

GithubリポジトリTravis CIと連携する方法

sue445.hatenablog.com

複数rubyのバージョンでテストをする

bundle gem コマンドで .travis.yml が作られているのでよしなに編集すればすぐに複数バージョンでのテストができます

rvm:
  - 2.0.0-p598
  - 2.1.6

  # こういう書き方をすれば2.2.x系の最新版を使う
  - 2.2

  # 開発中のrubyのバージョン(いわゆるtrunk)
  - ruby-head

matrix:
  allow_failures:
  # 開発中のrubyでテストがこけることはあるだろうから許容する
  - rvm: ruby-head

複数のgemのバージョンでテストをする

例として、複数Railsのバージョンでテストしてみます

.travis.yml のgemfileにこういうのを書いて

gemfile:
  - gemfiles/rails3_2.gemfile
  - gemfiles/rails4_0.gemfile
  - gemfiles/rails4_1.gemfile
  - gemfiles/rails4_2.gemfile

gemspec

  spec.add_dependency "rails"
  # 下記のように書いても可
  # spec.add_dependency "rails", ">= 3.2.0", "< 5.0"

バージョンごとにgemfileを必要なだけ用意するだけでok

source 'https://rubygems.org'

gem 'rails', "~> 4.2.0"

gemspec :path => '../'

補足

普通のgemでRailsの全ての機能に依存することはあまりないと思うので、actuverecordやactivesupportなどを指定するとベストです

参考

sue445.hatenablog.com

【おまけ】Rails 3系と4系でテストはしたいけど、Rails 4系でしか使えないgemも使いたい

とある社内gemで activerecord-turntable の対応をしてたのですが*1Rails 3系と4系の両方をサポートする必要がありました

普通にやろうとすると「Bundler could not find compatible versions 」ってエラーになるのでちょっとした工夫が必要でした

    multiple_version_test_sample (>= 0) ruby depends on
      rails (= 3.2.21) ruby depends on
        activerecord (= 3.2.21) ruby depends on
          activemodel (= 3.2.21) ruby depends on
            activesupport (= 3.2.21) ruby

    activerecord-turntable (>= 2.0.0) ruby depends on
      activesupport (>= 4.0.0) ruby

苦肉の策でグローバル変数経由でrailsのバージョンを渡すことで、Rails3系でテストする時にはgemspec内に存在しないようにしました('A`)

gemfile

source 'https://rubygems.org'

$rails_version = "4.2.0"
gem 'rails', "~> #{$rails_version}"

gemspec :path => '../'

gemspec

  # NOTE: turntableはrails4系でしか動かないのでgemfileでrails3系を使ってる時にはdependencyに追加しない
  if !$rails_version || $rails_version.to_i == 4
    spec.add_development_dependency "activerecord-turntable", ">= 2.0.0"
  end

Travis系の参考URL

Jenkinsを使う

Jenkinsのプロジェクト作成時に「マルチ構成プロジェクトのビルド」を使えばよいです

f:id:sue445:20150517023318p:plain

マトリックスの設定」のユーザ定義に変数名と値を追加すれば、この変数がexportされた状態でJenkinsのビルドがされるのでビルドスクリプト内でこの環境変数を使えば切り替えることは可能です

f:id:sue445:20150517025544p:plain

ビルド結果

f:id:sue445:20150517030109p:plain

ビルドスクリプトのサンプル

#!/bin/bash -xe

export LANG=ja_JP.UTF-8

if [ -n "${GEMFILE}" ]; then
  export BUNDLE_GEMFILE=gemfiles/${GEMFILE}
  echo "use ${BUNDLE_GEMFILE}"
fi

# setup rbenv
if [ -n "${RUBY}" ]; then
  export RBENV_VERSION=${RUBY}
  rbenv which ruby
  rbenv versions
  rbenv version
  echo "use ruby ${RUBY}"
fi

which ruby
ruby --version

bundle check || bundle install --jobs=2 --retry=3

bundle exec rspec

exit 0

【注意点】Jenkins rbenv plugin を使わない

rbenv pluginがビルド変数に対応していないため(v0.016時点)*2

ビルドスクリプト内で RBENV_VERSION をexportしてrubyのバージョンを変える https://github.com/sue445/multiple_version_test_sample/commit/b421e4604548eaa5ffbd6b1fdaf36157fb490fd8

【注意点】Jenkinsのキューが詰まるのでslaveを増やす

f:id:sue445:20150517150356p:plain

1テストスイートが3分だとしても16通りあると48分かかるので、slaveを増やして同時に実行できるテストの並列数を増やした方がいいです。

f:id:sue445:20150517151523p:plain

カジュアルにslaveを増やせるようにchefやitamaeなどのプロビジョニングツールで環境構築を自動化できるとベスト

【おまけ】JenkinsのSlaveでrbenvを使う方法

Jenkinsからslaveに接続する時は non-interactive モードなので .bash_profile とかが読み込まれないので、rbenvみたいに.bash_proflleで初期化するやつが使えないです。

「起動方法」->「高度な設定」-> 「 Prefix Start Slave Command」に source ~/.bash_profile && (&&の後に空白が必要)を書いておけば Slaveの.bash_profile が読み込まれてrbenvが使えるようになります。

f:id:sue445:20150517143239p:plain

あと、「JVMオプション」に -Dfile.encoding=UTF-8 があるのはコンソールログの文字化け防止のため。

f:id:sue445:20150517024955p:plain

~/.bash_profile はこんな感じ

export RBENV_ROOT=/home/jenkins/.rbenv
export PATH="$RBENV_ROOT/bin:$PATH"
eval "$(rbenv init -)"

まとめ

Jenkinsでも頑張ればできないことはないけど、Travis CI使うのがめっちゃ楽。

*1:半年くらい前

*2:rvmのプラグインも試したけど別の理由でダメだった気がする(うろ覚え)