はじめに
以前Rubyに興味を持って文法などを調べたはいいものの、そのときはRubyで何かひとつのプログラムを書くまでには至らなかったので、気を取り直してEffective Rubyを片手に、ゲームっぽい何かのプログラムを試行錯誤しながら作ってみています。この記事では、その過程で得られた知見などを雑多にご紹介します。
勉強の目的が強いので、「最初はGemもRakeも何も使わず、ゼロからディレクトリ作ってファイル作って動かしてみる。必要性に応じてそれらを取り入れてみる」というスタンスでやってみています。それによって、RakeやGemが何のために存在していて、Rubyエンジニアはなぜそれらを使うのかを理解するのが狙いです。
この記事は、僕のように「とりあえずRubyはじめてみたけど、GemとかRakeとか併せて覚えることがいっぱいあって大変!」「プロジェクトのディレクトり構成とかどうするのが良いんだろう、いつまでも直下にソースファイル増殖させるわけにもいかないし。。。」といった悩みにとらわれて次の一歩がうまく踏み出せない方向けの記事です。もうバリバリRubyでお仕事されている方にはあまり役に立たないかと思いますので、ご了承ください。また、この記事ではRailsも一切登場しません。併せてご了承ください。
なお、この記事の成果(というほどでもありませんが、、、)は以下のGitHubリポジトリで公開しています。
https://github.com/chooyan-eng/mygame
(2016/11/14 修正)
公開時、ユニットテストの種類をMinitest
と書いていましたが、完全に僕の勘違いで、正しくはtest-unit
でした。@jnchitoさん、ご指摘&編集リクエストありがとうございます!勉強します。
(/修正)
シリーズ一覧
- ゲームっぽい何かを作りながらRubyを勉強する その1 〜ディレクトリ構成とかtest-unitとかRakeとか〜
- ゲームっぽい何かを作りながらRubyを勉強する その2 〜ファイル保存〜
本文
とりあえずディレクトリ構成を作ってみる
ゼロから作る、というと、まず悩むのがディレクトリ構成です。以前Javaでcommanderを作った時は、Gradleでビルドするということを決めていたのでGradleの作法に従って作ればよかったのですが、今回は全くのゼロからのスタートです。
とりあえず、Rubyに限らずプログラムに必要なのはソースコードとテストコード、ということで、以下のようにsrc
とtest
のディレクトりを作るところから始めてみました。
$ mkdir src test
$ tree
.
├── src
└── test
一般的なディレクトリ構成はまだよくわからないですが、とりあえずこれで進めて、いろいろ調べているうちに何か分かったら変えていく、という作戦で進めてみたいと思います。
とりあえずセーブ機能を作ってみる
ゲームと言ったらデータのセーブ、ということで、まずはファイルにデータを保存するプログラムを書いてみます。
とりあえず、ファイル名と保存対象の文字列を受け取って、その通りにファイルを保存するところまで。
src
直下にsave_manager.rb
を作ります。
module MyGame
# module for managing game data
module DataManager
def self.save(file_name, data)
File.open(file_name, 'w') do |f|
f.puts(data)
end
end
end
end
動作確認はまだしません。
main的なプログラムを書いてとりあえず動かしてみるのではなく、後述するtest-unitを使って、テストプログラムから動作を検証する、という方法で確認します。
テストコードをtest-unitで書いてみる
ソースを書いたら(もしくは書こうと思ったら)テストを書いて動作確認する、ということで、Ruby標準のtest-unitでとりあえずユニットテストを書いてみます。テストコードに慣れるためにも、極力main関数的なもので動作確認、というのは控えます。
ここでは以下の記事を参考にしています。
Ruby標準のテスティングフレームワークで手軽にテストコードを書く方法 - Qiita
require 'test/unit'
require '../src/save_manager.rb'
class TestSample < Test::Unit::TestCase
def test_greeting
MyGame::DataManager.save('sampledata.txt', 'hello sample data!')
end
end
$ ruby save_manager_test.rb
Loaded suite save_manager_test
Started
.
Finished in 0.001633499 seconds.
-------------------------------------------------------------------
1 tests, 0 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
-------------------------------------------------------------------
612.18 tests/s, 0.00 assertions/s
うんうん、どうやら動いていそうです。テストコードを実行すると、セーブデータ(になる予定)のテキストファイルもできていました。1
$ ls -l
合計 8
-rw-rw-r--. 1 vagrant vagrant 19 9月 24 21:04 sampledata.txt
-rw-rw-r--. 1 vagrant vagrant 193 9月 24 21:01 save_manager_test.rb
Rakefileでテストを実行する
さて、ここで一つ疑問が出てきます。
とりあえずテストコードを1ファイル作りましたが、実装が進むにつれてこれが2ファイル、3ファイルと増えてきた場合、どのようにして一括で実行すれば良いのでしょうか。さすがに1ファイルずつ実行するのは面倒です。
と思いながらEffective Rubyを読んでいたら、ちょうどその答と言える内容が書いていました。
つまり、Rakeを使う、という方法です。
RakeはRuby製のビルド管理ツールだそうで、JavaのGradleやMavenなどに相当する仕組みのようです。ビルド管理ツールですので、test-unitの実行以外にもいろいろできるみたいですが、とりあえず今はテストの実行だけ、Rakeでできるようにしてみます。
Effective Ruby の項目36を見ると、Rakeの設定はRakefile
というファイルに書いていくそうです。で、test
ディレクトリ以下にあるすべてのテストコードの実行は、以下のように記述します。2
require('rake/testtask')
Rake::TestTask.new do |t|
t.test_files = FileList['**/*_test.rb']
t.warning = true
end
Rakefile
を保存したら、rake test
で実行します。
が、この時点ではまだテストは失敗します。テストコードであるsave_manager_test.rb
のrequire
の書き方に問題があるのですが、この点については次の項目で説明します。
require で指定するパスを改善する
前述の通り、今の状態でrake test
を実行すると、以下のようなエラーが出てテストコードが失敗してしまいます。
$ rake test
/home/vagrant/.rbenv/versions/2.3.1/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- ../src/save_manager.rb (LoadError)
from /home/vagrant/.rbenv/versions/2.3.1/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require'
...省略
rake aborted!
Command failed with status (1): [ruby -w -I"lib" -I"/home/vagrant/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/rake-11.2.2/lib" "/home/vagrant/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/rake-11.2.2/lib/rake/rake_test_loader.rb" "test/save_manager_test.rb" ]
Tasks: TOP => test
(See full trace by running task with --trace)
どうやら「../src/save_manager.rb
なんてファイルないよ」、ということだそうです。でもさっきruby save_manager_test.rb
で実行したときはそんなこと言われず、save_manager.rb
もちゃんとrequire
できていたのに、と思って試行錯誤していると、以下のことがわかりました。
-
test
ディレクトリ内でruby save_manager_test.rb
と実行した場合は成功する - ホームディレクトリで
ruby test/save_manager_test.rb
と実行すると、cannot load such file -- ../src/save_manager.rb
と言われて失敗する(rake test
の時と同じエラー)- というわけで、
require
のパス指定は、実行時のカレントディレクトりからの相対パスになっているっぽい
- というわけで、
- 試しに
require './src/save_manager.rb'
のように書き換え、ホームディレクトリで実行すると正常にテストが成功する
まとめると、require
は、実行時のディレクトリからの相対パスでファイルを探しにいっているようです。
正確には、LOAD_PATH
が通ったところからのパスで探すことと、普通にRubyプログラムを実行した場合はカレントディレクトリにLOAD_PATH
が通る、ということなどが以下の記事にまとめられていました。
さて、記事に書かれている通り、"./src"から指定するのもなんか間抜けなので、 -I オプションを使って require(save_manager.rb)
と書けば良いように修正してみます。
まずはsave_manager_test.rb
のrequire
を以下のように修正し、
require 'test/unit'
require 'save_manager.rb'
class TestSample < Test::Unit::TestCase
...以下略
実行は以下のようにします。
$ ruby -I ./src ./test/io/save_manager_test.rb
ただ、これだとまだRakeの実行時にパスが指定できていないので、rake test
でも同様に実行できるよう、Rakefileにt.libs << './src'
の行を追加します。
require('rake/testtask')
Rake::TestTask.new do |t|
t.libs << './src'
t.test_files = FileList['**/*_test.rb']
...以下略
これでrake test
で実行したときもちゃんとsrc/save_manager.rb
を見つけてくれるようになりました。
ついでに、require
する際に.rbの拡張子がつくのも若干格好悪いと思って調べたところ、require
では拡張子は省略可能ということがわかりましたので、一緒に修正します。
結果、save_manager_test.rb
のrequire
部分は以下のようになりました。
require 'test/unit'
require 'save_manager'
...以下略
ちなみに修正前は以下です。
require 'test/unit'
require '../src/save_manager.rb'
...以下略
全然違いますね。
この変更は見た目のすっきりさ以外にも大きな利点があります。
修正前のように「このファイルからの相対パス」で書いてしまうと、今後ディレクトリ構成が変わったりファイルを別のディレクトリに移動したりしたときにrequire
の記述まで変えなくてはならない、という事態が発生します。
しかし、修正後のように「(設定で指定した)ソースディレクトリからの相対パス」としておくことで、テストファイルがどこにあっても変更なしで実行できるようになるのです。(ソースファイルが移動されたらさすがに要修正ですが。。。)
まとめ
ひとまずここまで、ソースコードとテストコードを書いて、テストを実行する、という土台が出来上がりました。まだまだゲームの完成には程遠いですが(というかどんなゲームにするのかも定かではないですが)、引き続き心の赴くままに作ってみて、分かったことがあればまとめていければと思います。
宣伝
記事中でもちょろっと出てきましたが、先日、同じような試みをJavaでもやりました。
こちらは"commander"という名前でとりあえず形になっていますので、Javaに興味のある方は見てみてください。
commander というコマンド実行履歴分析ツールを作りました