Born Too Late

Yuya's old tech blog.

Ubuntu 10.04 で JRuby on Rails on GAE を始めるまでの作業ログ

2010-07-25 15:14:32

注意

この記事は既に情報が古くなっています。
2010 年 7 月 26 日現在での最新の情報は以下を参照してください。

この記事では、GAE (Google App Engine) 上で Ruby on Rails を動かすまでに必要な作業を紹介します。

既に GAE を利用している人であればご存知かと思いますが、GAE では Java か Python しか動かすことができないので、通常は Ruby を動かすことはできません。しかし、Java が動くということは、JRuby は動くので、その中で Rails を動かしてみよう、というものです。

この記事は、ほとんどが @urekat さんによる記事のトレースです。ただし、現在は、@urekat さんの記事そのままでは動かない部分もあるので、そこも吸収しながら紹介していきます。

@urekat さんの記事が書かれたのが 2010 年 2 月 20 日で、半年も経たずにインストール作業が変わってしまっているので、この記事も数ヶ月後には役に立たなくなっているかもしれません。以下はすべて、2010 年 7 月 25 日現在の情報である、ということにご注意ください。

今回は、Ruby のインストールから始めます。

Ruby, Rubygems, Java のインストール

Ruby は MRI (Matz' Ruby Implementation) の 1.8.7 を用います。ここでは JRuby でないことにご注意ください。

Rubygems というのは、Ruby 用のパッケージ管理ソフトです。

Java は 1.6 を用います。

sudo apt-get install ruby rubygems openjdk-6-jdk

正常にインストールされれば、以下の用にバージョンの確認ができるはずです。

$ ruby -v
ruby 1.8.7 (2010-01-10 patchlevel 249) [i486-linux]
$ gem -v
1.3.5
$ java -version
java version "1.6.0_18"
OpenJDK Runtime Environment (IcedTea6 1.8) (6b18-1.8-0ubuntu1)
OpenJDK Client VM (build 14.0-b16, mixed mode, sharing)

Rails のインストール

先ほどインストールした Rubygems でインストールできます。

$ sudo gem install rails --version 2.3.5 --no-ri --no-rdoc
Successfully installed rake-0.8.7
Successfully installed activesupport-2.3.5
Successfully installed activerecord-2.3.5
Successfully installed rack-1.0.1
Successfully installed actionpack-2.3.5
Successfully installed actionmailer-2.3.5
Successfully installed activeresource-2.3.5
Successfully installed rails-2.3.5
8 gems installed

現在、Rails は 2.3.8 が最新ですが、rails_appengine パッケージが 2.3.5 を必要賭しているようなので、2.3.5 を指定しています。

--no-ri --no-rdoc オプションは、ドキュメントのダウンロードをスキップするものです。ドキュメントが必要であれば、これらのオプションは外して構いません。

google-appengine パッケージのインストール

ここでは、先ほどインストールした Rubygems でインストールを行います。

$ sudo gem install google-appengine --no-ri --no-rdoc
Successfully installed jruby-jars-1.5.1
Successfully installed appengine-rack-0.0.11
Successfully installed appengine-apis-0.0.18
Successfully installed appengine-sdk-1.3.5
Successfully installed bundler08-0.8.5
Successfully installed rubyzip-0.9.4
Successfully installed appengine-tools-0.0.15
Successfully installed google-appengine-0.0.15
8 gems installed

これだけで GAE 関連のライブラリ・ツールが一気にインストールされます。

しかし、通常このままでは appcfg.rb や dev_appserver.rb といったツールにパスが通っていないと思うので、Rubygems の bin ディレクトリにパスを通します。好みのエディタで ~/.bashrc ファイルを開き、一番下にでも以下のような記述を追加します。

# Rubygems
export PATH=$PATH:/var/lib/gems/1.8/bin

続いて、~/.bashrc を再読み込みすれば反映されます。

source ~/.bashrc

パスが通ったか、の確認は以下のように行うことができます。

$ which appcfg.rb dev_appserver.rb
/var/lib/gems/1.8/bin/appcfg.rb
/var/lib/gems/1.8/bin/dev_appserver.rb

ここまでは @urekat さんの記事とほとんど変わりません。ここから先に、変更点を多く含みます。

プロジェクトを作成する

ここでいう「プロジェクト」とは「ひとかたまりの Rails アプリケーション」ぐらいの意味です。そのテンプレートにあたるファイル群を生成します。

プロジェクトはひとつのディレクトリ内で行うので、適当にディレクトリを作成し、その中に移動します。

$ mkdir test_app
$ cd test_app

この中で、先ほどインストールし、パスを通した appcfg.rb コマンドを使います。最後の引数の . (ドット) に注意してください。

$ appcfg.rb generate_app .
=> Generating gemfile
=> Bundling gems
Calculating dependencies...
Updating source: http://gems.rubyforge.org
Downloading appengine-rack-0.0.11.pre.gem
Caching: jruby-jars-1.5.1.gem
Caching: jruby-rack-1.0.1.gem
Caching: rack-1.2.1.gem
Installing jruby-jars (1.5.1)
Installing rack (1.2.1)
Installing jruby-rack (1.0.1)
Installing appengine-rack (0.0.11.pre)
Done.
=> Packaging gems
=> Installing jruby-core-1.5.1.jar
=> Installing jruby-stdlib-1.5.1.jar
=> Installing jruby-rack-1.0.1.jar
=> Installing appengine-rack.jar
=> Generating rackup
=> Generating app.yaml

これで、GAE 上で JRuby アプリケーションを動かすためのテンプレートが生成されました。

私自身は試していませんが、例えば Sinatra を GAE 上で動かす、というときも、ここまでは同じ作業が必要になると思います。

次に、Rails のファイル群を生成します。途中「robots.txt を上書きしてもいいですか ?」と聞かれますが、Yes で問題ありません。

$ wget http://appengine-jruby.googlecode.com/hg/demos/rails2/rails2_appengine.rb
$ ruby rails2_appengine.rb
=> Bundling gems
Calculating dependencies...
Updating source: http://gems.rubyforge.org
Caching: actionmailer-2.3.5.gem
Caching: actionpack-2.3.5.gem
Caching: activerecord-2.3.5.gem
Caching: activeresource-2.3.5.gem
Caching: activesupport-2.3.5.gem
Downloading addressable-2.1.2.gem
Caching: appengine-apis-0.0.18.gem
Caching: appengine-rack-0.0.11.gem
Downloading dm-appengine-0.0.9.gem
Downloading dm-ar-finders-0.10.2.gem
Downloading dm-core-0.10.2.gem
Downloading dm-timestamps-0.10.2.gem
Downloading dm-validations-0.10.2.gem
Downloading extlib-0.9.15.gem
Caching: rack-1.0.1.gem
Caching: rails-2.3.5.gem
Downloading rails_appengine-0.0.4.gem
Downloading rails_dm_datastore-0.2.9.gem
Caching: rake-0.8.7.gem
Installing activesupport (2.3.5)
Installing extlib (0.9.15)
Installing rails_appengine (0.0.4)
Installing rack (1.0.1)
Installing actionpack (2.3.5)
Installing actionmailer (2.3.5)
Installing rake (0.8.7)
Installing addressable (2.1.2)
Installing dm-core (0.10.2)
Installing dm-timestamps (0.10.2)
Installing dm-validations (0.10.2)
Installing dm-appengine (0.0.9)
Installing appengine-rack (0.0.11)
Installing appengine-apis (0.0.18)
Installing dm-ar-finders (0.10.2)
Installing activerecord (2.3.5)
Installing rails_dm_datastore (0.2.9)
Installing activeresource (2.3.5)
Installing rails (2.3.5)
Deleting gem: rack (1.2.1)
Deleting gem: appengine-rack (0.0.11.pre)
Done.
=> Packaging gems
=> Installing appengine-api-labs-1.3.5.jar
=> Installing appengine-api-1.0-sdk-1.3.5.jar
=> Installing jruby-core-1.5.1.jar
=> Installing jruby-stdlib-1.5.1.jar
=> Installing jruby-rack-1.0.1.jar
=> Installing appengine-rack.jar
      exists
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts
      create  config/environments
      create  config/initializers
      create  config/locales
      create  db
      create  doc
      create  lib
      create  lib/tasks
      create  log
      create  public/images
      create  public/javascripts
      create  public/stylesheets
      create  script/performance
      create  test/fixtures
      create  test/functional
      create  test/integration
      create  test/performance
      create  test/unit
      create  vendor
      create  vendor/plugins
      create  tmp/sessions
      create  tmp/sockets
      create  tmp/cache
      create  tmp/pids
      create  Rakefile
      create  README
      create  app/controllers/application_controller.rb
      create  app/helpers/application_helper.rb
      create  config/database.yml
      create  config/routes.rb
      create  config/locales/en.yml
      create  db/seeds.rb
      create  config/initializers/backtrace_silencers.rb
      create  config/initializers/inflections.rb
      create  config/initializers/mime_types.rb
      create  config/initializers/new_rails_defaults.rb
      create  config/initializers/session_store.rb
      create  config/environment.rb
      create  config/boot.rb
      create  config/environments/production.rb
      create  config/environments/development.rb
      create  config/environments/test.rb
      create  script/about
      create  script/console
      create  script/dbconsole
      create  script/destroy
      create  script/generate
      create  script/runner
      create  script/server
      create  script/plugin
      create  script/performance/benchmarker
      create  script/performance/profiler
      create  test/test_helper.rb
      create  test/performance/browsing_test.rb
      create  public/404.html
      create  public/422.html
      create  public/500.html
      create  public/index.html
   identical  public/favicon.ico
overwrite public/robots.txt? (enter "h" for help) [Ynaqdh] y
       force  public/robots.txt
      create  public/images/rails.png
      create  public/javascripts/prototype.js
      create  public/javascripts/effects.js
      create  public/javascripts/dragdrop.js
      create  public/javascripts/controls.js
      create  public/javascripts/application.js
      create  doc/README_FOR_APP
      create  log/server.log
      create  log/production.log
      create  log/development.log
      create  log/test.log
+ ./LICENSE
+ ./README
+ ./Rakefile
+ ./init.rb
+ ./lib/active_record/connection_adapters/nulldb_adapter.rb
+ ./lib/nulldb_rspec.rb
+ ./spec/nulldb_spec.rb
+ ./tasks/database.rake
##
## Now type './script/server.sh'
##

最後に、Gemfile というファイルに以下の記述を追加します。

gem 'appengine-rack', '0.0.10.pre'
gem 'jruby-openssl'
gem 'jruby-jars'

appengine-rack パッケージは、現在 0.0.11 が最新のようですが、どうもそれだと動かないようなので、0.0.10.pre を指定しています。

開発サーバーを起動する

以上が問題なく完了すれば、開発サーバーを起動して、動作確認してみましょう。

$ dev_appserver.rb .
=> Bundling gems
Calculating dependencies...
Updating source: http://gems.rubyforge.org
Downloading appengine-rack-0.0.10.pre.gem
Downloading jruby-openssl-0.7.gem
Installing jruby-openssl (0.7)
Installing appengine-rack (0.0.10.pre)
Deleting gem: appengine-rack (0.0.11)
Done.
=> Packaging gems
=> Installing appengine-rack.jar
=> Installing appengine-api-labs-1.3.5.jar
=> Installing appengine-api-1.0-sdk-1.3.5.jar
=> Installing jopenssl.jar
=> Installing bcmail-jdk15-144.jar
=> Installing bcprov-jdk15-144.jar
=> Installing jruby-core-1.5.1.jar
=> Installing jruby-stdlib-1.5.1.jar
=> Installing jruby-rack-1.0.1.jar
=> Booting DevAppServer
=> Press Ctrl-C to shutdown server
# 中略
情報: The server is running at http://localhost:8080/

初回は上記の用に、必要な Rubygems を jar ファイルとしてインストールする動作が伴うので時間がかかりますが、それ以降は普通にサーバーが起動するのみです。

http://localhost:8080/ にアクセスすれば、Rails のデフォルト画面が表示されているはずです。

「About your application’s environment」というテキストをクリックすると、以下のように表示されるはずです。

Ruby version	1.8.7 (java)
RubyGems	disabled
Rack version	1.1
Rails version	2.3.5
Action Pack version	2.3.5
Active Support version	2.3.5
DataMapper version	0.10.2
Environment	development
JRuby Runtime version	1.5.1
JRuby-Rack version	1.0.1
AppEngine SDK version	1.3.5
AppEngine APIs version	0.0.18
Auth domain	gmail.com
Application id:version	new-app:1

ここまでで、Ubuntu 10.04 のローカル環境において、GAE 開発サーバー上での JRuby on Rails の準備は終わりです。

その後どうするか

このあとは、自分なりのアプリケーションを開発したり、それを GAE 本番環境にデプロイしたりとしていくわけですが、それらについては @urekat さんの記事のままで問題ないようなので、ここでの説明は省きます。

これ以降については私自身も知らない部分が多いので、自分でアプリを作っていきながら、紹介していければと思います。

JRuby on Rails に興味を持った方は、日本 JRuby ユーザ会のイベントに参加してみましょう ! 次回 2010 年 8 月 6 日のイベントでは、@urekat さんの GAE についてのプレゼンも聞けます。

関連ページ

PHPUnit によるテスト駆動開発 #03 コードを育てる編

2010-07-18 15:26:14

前回のおさらい

前回『#02 テストを書いてみる編』では、「とりあえず、とにかく、テストを書き、PHPUnit で実行すること」という目標のもと、簡単なテストコードの書き方を学びました。

今回の目標

テストコードを拡張しながら、仕様変更に対応する。また、リファクタリングする。

仕様変更

前回は、引数に 2 つの数字を引数に受け取り、その合計値を返すだけの add メソッドを作成しました。

今回は、数字が 2 の場合だけでなく、数字が 3 つの場合も、同じように合計値を返す必要が出てきたと仮定し、話を進めます。

テストコードを拡張する

add メソッドの他に addThreeNumbers メソッドを作ることもできますが、これでは足し算をする際に、数値が 2 つなのか 3 つなのかを意識しないといけないので、ナンセンスです。どちらの場合も add メソッドで処理できるようにしましょう。

前回はは「実際のコード」 -> 「テストコード」という順序でしたが、今度は「テストコード」を先に書いてみます。つまり、「テストファースト」です。

testAddThreeNumbers メソッドを追加しました。

ここで一旦話はそれますが、テストメソッドの名前を test~ とするのは、PHPUnit のルールです。test~ という名前のメソッドだけが、テストとして実行されます。 一応、その他にもテストとして実行させるためのルールはありますが、とりあえず置いておきます。

もし、テストケースの中だけで使うサブルーチン的メソッドが必要であれば、test~ とは違う名前をつければ、そのメソッドはテストとして実行されることはありません。

それでは話を元に戻し、このままテストを実行しましょう。

$ phpunit CalcTest.php
PHPUnit 3.4.15 by Sebastian Bergmann.

.F

Time: 0 seconds, Memory: 4.75Mb

There was 1 failure:

1) CalcTest::testAddThreeNumbers
Failed asserting that <integer:2> matches expected <integer:3>.

/path/to/CalcTest.php:16

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

当然失敗します。Calc オブジェクトの add メソッドはまだ、2 つの数値の足し算にしか対応していません。

一見無駄なように見えますが、この「初めに失敗する」というプロセスも、テスト駆動開発 (Test Driven Development; TDD) においては重要です。そもそものテストコードが間違っていた場合、「常に成功する」という状態になっていることもあり得るからです。

「初めは失敗するが、コードを正しく実装することにより初めて成功する」というプロセスを踏むことによって、より堅実に TDD を行うことができるのです。

コードを育てる

それでは、追加された仕様、そしてテストコードに対応すべく、実際のコードを育ててみましょう。前回のコードは以下のようになっていました。

これを、以下のように書き換えます。

条件分岐により、第 3 引数がセットされていた場合の処理を追加しています。

ここで再びテストを実行します。

$ phpunit CalcTest.php
PHPUnit 3.4.15 by Sebastian Bergmann.

..

Time: 0 seconds, Memory: 4.75Mb

OK (2 tests, 2 assertions)

見事成功しました !

この成功は、今回の「3 つの数値の合計値を返す」が達成できただけではなく、前回作った「2 つの数値の合計値を返す」をも損なうこと無く、コードを育てることができたことを意味します。

テストコードにおける共通処理を一元化する

仕様変更への対応が終わったので、今できているテストコードを振り返ってみましょう。

この記事の冒頭で、testAddThreeNumbers メソッドを作りましたが、「Calc オブジェクト生成」のための記述が、testAdd メソッドと重複していることがわかります。

このような共通処理は、setUp メソッドにより一元化させることができます。

このように、Calc オブジェクトの生成を一元化することで、各テストメソッドで同じことを繰り返す必要がなくなりました。これは DRY (Don't Repeat Yourself; 同じことを繰り返さない) の原則に合致していると言えるでしょう。

この例のような短いコードにおいては、コードの絶対量が増えてしまっていますが、実際の開発において、テストコードはどんどん増えていくものですから、特に理由の無い限りは、setUp メソッドを使って共通化させましょう。

なお、setUp メソッドは、それぞれのテストメソッドが実行される前に、フックされる形で実行されます。なので、testAdd メソッドの $this->calc と、testAddThreeNumbers の $this->calc は、オブジェクトとしては別物ということに注意しましょう。

リファクタリング

さて、今度は実際のコードを振り返ってみましょう。

今回の改修では、if の条件分岐により、引数が 2 つでも 3 つでも合計値を返すよう、処理しています。

しかし今後、4 つの数値を合計も返さないといけなくなってしまったら、どうしましょう。一番簡単なのは、コピペ的に else if で分岐していき、引数が 4 つの場合も同じように処理することです。

しかし、それがまた、5 つ 6 つと増えて行ったら・・・。

この問題を解決するには、add メソッドの処理を根本的に書き換えなくてはなりません。今後のためとはいえ、今動いているものが壊れてしまうかもしれない、というリスクを犯してまで、リファクタリングする必要があるのでしょうか。

この場合、TDD であれば、リファクタリングを取ります。リファクタリングがある程度のリスクを抱える行為だとしても、それを安全に行うことができるのであれば、そして、放置する方が将来より大きなリスクとなることがわかっているのであれば、どちらを選ぶべきかは自明です。

では、今度は、引数がいくつだろうと処理できるよう、add メソッドをリファクタリングしてみましょう。

さっきと同じく、先にテストコードから書きます。

testAddTenNumbers メソッドを書いて、引数が 10 個の場合のテストを追加しました。これを実行すると、やはり失敗します。

$ phpunit CalcTest.php
PHPUnit 3.4.15 by Sebastian Bergmann.

..F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) CalcTest::testAddTenNumbers
Failed asserting that <integer:6> matches expected <integer:55>.

/path/to/CalcTest.php:26

FAILURES!
Tests: 3, Assertions: 3, Failures: 1.

今度は、このテストが成功するよう、実際のコードをリファクタリングします。

func_get_arg() を使って、可変引数に対応できるよう、リファクタリングを行いました。再びテストを実行します。

$ phpunit  CalcTest.php
PHPUnit 3.4.15 by Sebastian Bergmann.

...

Time: 0 seconds, Memory: 5.00Mb

OK (3 tests, 3 assertions)

見事成功しました !

元のコードを根本的に書き換えていますが、数値が 2 つ、もしくは 3 つの場合の動作も壊すこと無く、引数がいくつ来ても合計値を返すことができるようになりました。

まとめ

今回は、以下のことを学びました。

今回は、前回一旦無視した「テストファースト」というスタイルにのっとり、解説を行いました。

ですが、実際の開発においては、より大きなプログラムを扱うこととなるため、クラス設計がしっかりできる人で無い限り、「テストファースト」の実践は難しいでしょう。

しかし、TDD を実践し続けることで、クラス設計のセンスも向上する、とも言われています。次回はその辺りの、TDD がもたらす副産物について話を進めて行きます。

PHPUnit によるテスト駆動開発 #02 テストを書いてみる編

2010-07-17 14:51:50

目的

とりあえず、とにかく、テストを書き、PHPUnit で実行すること。

想定される読者

TDD を知らない、PHPUnit をまだインストールしていない、という方は前回の記事『#01 インストール編』をご覧ください。

逆に、上記項目より上を行く方にとっては、特に参考になることは無いかもしれません。

テストされるコード

初めに、実際にプログラム上で使われるコードを用意します。

TDD の世界には「テストファースト」という言葉があります。それは、「実際のコードよりもテストコードを先に書く」というスタイルを表していますが、ここではそういった細かいことは気にせず話を進めます。

2 つの引数を足し算するだけの簡単なメソッドを持った、非常にシンプルなクラスです。

テストするコード

以下のようなコードを用意します。

これが、PHPUnit でテストを書く際の最小単位である、テストケースです。

注目すべきは、testAdd メソッドの中身です。これは、assertEquals というメソッドが、「$calc オブジェクトの add メソッドに、引数として 1 と 1 を渡せば、2 になるよね」といったことを確かめるためのコードです。

その他の部分は、PHPUnit でテストケースを書くためのおまじないみたいなものです。細かいルールについては次回以降、徐々に説明するとして、とりあえず次に進みます。

テストを実行する

ターミナル上で以下のコマンドを実行します。

$ phpunit CalcTest.php
PHPUnit 3.4.15 by Sebastian Bergmann.

.

Time: 1 second, Memory: 4.75Mb

OK (1 test, 1 assertion)

最後の行の「OK」は、このテストが成功し、コード (Calc.php) が正確に実装されていることを示しています。また、1 つのテスト (1 test) と 1 つのアサーション (1 assertion) が実行された、ということも示されています。

もし add メソッドに間違いがあった場合は、PHPUnit の実行結果は以下のようになります。

$ phpunit CalcTest.php
PHPUnit 3.4.15 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 4.75Mb

There was 1 failure:

1) CalcTest::testAdd
Failed asserting that <integer:1> matches expected <integer:2>.

/path/to/CalcTest.php:10

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

これは、add メソッドが 2 を返すはずなのに、実際は 1 が返ってきてしまったために、テストが失敗してしまったことを示しています。

このような場合は、テストが成功するまでコードを修正しましょう。

アサーションとは

アサーションとは、変数の値や、メソッドの返り値をチェックすることです。さっきのテストケースでいえば、assertEquals メソッドがそれを行っています。

ざっくり言ってしまえば、前回からの繰り返しになりますが、「このメソッドにこの引数を与えると、この値が返ってくるよね」といったことをチェックすること、と言えるでしょう。

アサーションには多くの種類があり、例を挙げれば、

などなど、たくさんあります。

ですが、とりあえずは assertEquals と assertTrue だけ使えれば大抵のことは何とかなるので、他は必要に応じて調べながら覚えていけばよいでしょう。

まとめ

今回は、テストを書くこと、そして実行すること、に的を絞って解説しました。

次回は、テストコードを拡張させながら、PHPUnit でテストを書く際のルールについて、もう少し踏み込んでいこうとおもいます。