土屋つかさの技術ブログは今か無しか

土屋つかさが主にプログラミングについて語るブログです。

rubyゲーム開発にユニットテスト/テスティングフレームワークを導入する(6)【CI/AppVeyor編】

 長々とやってきました「rubyゲーム開発にユニットテスト/テスティングフレームワークを導入する」シリーズもひとまず今回で一区切りになります(なぜならそろそろ作業を区切って同人誌の原稿執筆に入らないといけないからだ!)。今回はCIの導入です。

CIとはなにか

 集団で巨大なソフトウェアをコーディングする際、個々のプログラマは日々ローカル環境でビルド&テストを行いますが、共有リポジトリ内の最新コードに対してビルド&テストを定期的に行い、コードの安定性を確認するのは人力で行うには高コストです。この作業を自動化する試み、あるいはそのサービスのことををCI(continuous integration:継続的インテグレーション)と呼びます。

継続的インテグレーション
https://ja.wikipedia.org/wiki/%E7%B6%99%E7%B6%9A%E7%9A%84%E3%82%A4%E3%83%B3%E3%83%86%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3

 CI支援ソフトウェアの中で一番有名なのは恐らくJenkinsでしょう。Jenkinsはリポジトリからのコミット通知を受けて自身のサーバー上にコードをダウンロード→ビルド→テストなどの一連の作業を行います。これによって常に最新のコードに対してテストが適用され、またパッケージがアップされ、定期ビルドの手間が削減されるのです。

 Rubyでは広義のビルドタイミングがありませんが、実行時の構文エラーやユニットテストにおいてCIの支援を受けることができます。

AppVeyorについて

 今回はGitHub上の司エンジンのリポジトリに対して、AppVeyorでのCIが機能するようにします。AppVeyorはオープンソースプロジェクトに対して無償でCIサービスを提供してくれる有難いサービスです。この手の無償サービスではTravisCIの方が有名なのですが、
今回は使えません。何故なら司エンジンのテストを行うにはWindows環境でなければならないからです。

 TravisCIはLinux環境でのCIが提供されるのですが、AppVeyorはなんとWindows環境でのCIが提供されます。土屋は実践してませんが、UIを伴ったリモートコントロールも可能なのだそうです(どうにもビルドがこける時とかに使う)。すごいなAppVeyor!

AppVeyorの環境設定

 APpVeyorでのアカウント作成からGitHubとの連携までの流れについてはこちらの記事が詳しいのでそちらをどうぞ。

How to use AppVeyor (AppVeyorの使い方)
http://qiita.com/takahashim/items/1851b6c3e05bb140bb09

司エンジンでの環境設定

 GitHubリポジトリのルートにappveyor.ymlファイルを配置します。YAML形式でAppVeyorで実行される各タスクでシェルで実行される処理を記述していきます。

appveyor.yml

# appveyor.yml
install:
  - set PATH=C:\Ruby22\bin;%PATH%
  - set APPVEYOR_BUILD=true
  - gem install bundler
  - bundle install

build: off

before_test:
  - ruby -v
  - gem -v
  - bundle -v

test_script:
  - bundle exec rake spec

 "bundle install"によって、下記のGemFileが実行され、司エンジンの実行に必要なgemがインストールされます。

GemFile

# frozen_string_literal: true
source "https://rubygems.org"

gem "rake"
gem "rspec"
gem "dxruby"
gem "parslet"

 "bundle exec rake spec"で実行されるspecタスクは、rakefileで以下のように記述されています。内容は前回と変わりませんが、タスクが宣言的に記述されるのがどうにも気持ちわるかったのでdo〜endで囲むようにしました。

rakefile

task :spec do

	require "rspec/core/rake_task"
	RSpec::Core::RakeTask.new("spec")

end

ビルド

 この状態でGitHubにコミットすると、自動的にAppVeyor上でテストが実行され、ログが保存されます。



 繰り返しになりますが、これの何が凄いのかというと、司エンジンのテストコードのほとんどはDXRuby/DirectXを経由してアプリウィンドウに描画を行っています。そのテストコードが通っているということは、AppVeyor上でゲーム画面が表示されているということです(確認はしていませんが、リモートコントロールで確認できる筈です)。

 AppVeyorはビルドの結果を示すバッジを提供しているので、これをGitHub側で設定することで、最新のコードのビルドが通っていることを示すことができます。これによって、コードの安定性を第三者に示すことができます。

終わりに

 昨年11月末に友達の超ウィザード級エンジニアと飲んだ時に、「ゲームのコードでのユニットテストは現実的ではない」と言った時「違う。テストがしやすいようにコードを最適化するんだ」と返されて目からウロコが落ち、その方からアドバイスをもらいながら、ゲーム開発でのユニットテスト導入方法を模索してみました。

 CIまで実現出来たので、これでユニットテスト周りの技術を一通り触ったことになります。まさか2ヶ月半かかるとは思わなかったよ!w
 さきほども書いた通り、今回の内容は整理/加筆して同人誌の原稿とし、技術書典2で頒布予定なので、どうぞよろしくお願いします。
 みなさん、ゲーム開発でのCIベースによるテスト駆動開発は可能です!><

補足

 実はAppVeyor対応時にどうにも上手く行かなかったことが一個あります。司エンジンではBGM/SEの再生にAyame/rubyを利用しているのですが、このライブラリのrequireしている最中にAyame.dllの読みこみに失敗するのを解決する方法がわかりませんでした。

 ひとまず緊急回避として、appveyor.ymlのinstallタスク時に環境変数APPVEYOR_BUILDに値を設定し、Sound.rb内の該当箇所をスキップさせました。

Sound.rb

#TODO将来的にはプリミティブライブラリへの依存を解消したい
#TODO現状appveyor上でAyame.dllをロードする方法が不明なため緊急回避
unless ENV['APPVEYOR_BUILD'] == "true"
  require_relative './ayame.so'
end

 これでひとまずビルドは通るようになりました。