JavaのライブラリのテストをJRuby+minitestで書く
お前は何と戦っているんだ感が半端ないですが、タイトルの通りJavaで書いたライブラリのテストをJRuby+minitestで書いてみました。
テストを書く対象のライブラリは、よくある(?)行列演算を行うやつです。
例としてMyMatrixLib.jar (net.rkmathi.lib.MyMatrixLib)
という名前にしてみます。
JRuby, minitestのインストール
rbenvを使っているので、それ経由で入れました。
$ rbenv install -l | grep jruby ... jruby-1.7.16 jruby-1.7.16.1 jruby-9.0.0.0-dev jruby-9000-dev ...
jruby-1.7.16.1にしました。
$ rbenv install jruby-1.7.16.1
テスト書くディレクトリに移動して、rbenv local
でjrubyを使うようにします。
$ cd /path/to/test_directory $ rbenv local jruby-1.7.16.1 $ rbenv rehash $ jruby --version jruby 1.7.16.1 (1.9.3p392) 2014-10-28 4e93f31 on Java HotSpot(TM) 64-Bit Server VM 1.8.0_25-b17 +jit [darwin-x86_64]
minitestのインストールをします。
$ jruby -S gem install minitest
ライブラリのJARを持ってくる
$ cd /path/to/test_directory $ cp /path/to/MyMatrixLib.jar .
JRuby+minitestでテストを書く
とりあえず、get()とplus()とminus()のテストを書いてみるとこうなります。
require 'minitest' require 'minitest/autorun' require 'java' require_relative './MyMatrixLib.jar' import 'net.rkmathi.lib.MyMatrixLib' class TestMyMatrixLib < Minitest::Test def setup @d1 = rand(1000.0) @d2 = rand(1000.0) @m1 = MyMatrixLib.new(3, 2, @d1) # 3x2ですべての要素が@d1の行列を生成 @m2 = MyMatrixLib.new(3, 2, @d2) # 3x2ですべての要素が@d2の行列を生成 end # get(r, c) => double: 行列のr行c列目の要素を取得 def test_get each_assert_get @m1, @d1 each_assert_get @m2, @d2 end # plus(matrix) => MyMatrix: 行列のそれぞれの要素を足した行列を生成 def test_plus d_res = @d1 + @d2 m_res = @m1.plus(@m2) # @m1と@m2のそれぞれの要素を足した結果の行列 each_assert_get m_res, d_res end # minus(matrix) => MyMatrix: 行列のそれぞれの要素を引いた行列を生成 def test_minus d_res = @d1 - @d2 m_res = @m1.minus(@m2) # @m1と@m2のそれぞれの要素を引いた結果の行列 each_assert_get m_res, d_res end private # 行列の各要素をそれぞれgetするのが面倒なのでメソッド化 def each_assert_get(m, d) for r in 0...m.get_row_dimension do for c in 0...m.get_column_dimension do assert_equal m.get(r, c), d end end end ... end
と、こんな感じで書くことができます。
ちなみにJUnitだと...
public class MyMatrixLibTest { @Before public void setUp() throws Exception { MyMatrixLib d1 = Math.random() * 1000.0; MyMatrixLib d2 = Math.random() * 1000.0; MyMatrixLib m1 = new MyMatrixLib(3, 2, d1); MyMatrixLib m2 = new MyMatrixLib(3, 2, d2); } @Test public void testGet() { eachAssertGet(m1, d1); eachAssertGet(m2, d2); } @Test public void testPlus() { double d_res = d1 + d2; MyMatrixLib m_res = m1.plus(m2); eachAssertGet(m_res, d_res); } @Test public void testMinus() { double d_res = d1 - d2; MyMatrixLib m_res = m1.minus(m2); eachAssertGet(m_res, d_res); } private void eachAssertGet(MyLibMatrix m, double d) { for (int row = 0; row < m.getRowDimension(); row++) { for (int column = 0; column < m.getRowDimension(); column ++) { assertEquals(m.get(row, column), d); } } } }
簡単な演算のテスト部分しか比較しませんでしたが、書いてて楽には思えました。
ですが、JRubyを書いていると文法とか変数の型をミスった時に吐かれる例外がよくわからないことになることもあるので、わざわざJRubyでテスト書くのは微妙かも...
VPS上にSoftEther VPN ServerをセットアップしてOS Xからつなぐ
HDD容量を少なくすればめちゃ安いVPS( http://www.idcf.jp/cloud/ )を教えてもらったので、ここにSoftEtherVPN Serverをセットアップしてみました。
ちなみに、仮想マシンを作成するときに「テンプレート」からだとHDD容量が最低15GBなので540円/月ですが、「ISO」から作るようにすると最低1GBから選べるようになります。例えば5GBにすれば324円/月で済みます。
ポートの設定
VPSのコントロールパネル側でファイアウォールの設定があるので、
のポートを開けておきます。
SoftEther VPNのダウンロード
SoftEther ダウンロードセンターから、SoftEther VPN (Freeware)
→SoftEther VPN Server
→Linux
→Intel x64 / AMD64 (64bit)
を選んで、最新ビルドのリンクのアドレスをメモっておきます。
あとで、OSをインストールしたらこのリンクから引っ張ってきます。
OSのインストールとファイルの配置
適当に、Ubuntu Server 14.04 amd64
をインストールしておきました。
### 必要なパッケージのインストール ~$ sudo aptitude update ~$ sudo aptitude upgrade ~$ sudo aptitude install build-essential sysv-rc-conf ### さっきのリンクからダウンロード ~$ wget http://jp.softether-download.com/files/XXX/softether-vpnserver-XXX-linux-x64-64bit.tar.gz ~$ tar xvf softether-vpnserver-XXX-linux-x64-64bit.tar.gz ~$ cd vpnserver ### 確認したら、1でAcceptしてmake ~/vpnserver$ make 1 1 1 ### vpnserverのパーミッションを変更 ~/vpnserver$ chmod 600 * ~/vpnserver$ chmod 700 vpncmd vpnserver ~/vpnserver$ cd ../ ~$ sudo mv vpnserver /usr/local/ ~$ sudo chown -R root:root /usr/local/vpnserver ~$ cd /usr/local/vpnserver ### 動作確認 /usr/local/vpnserver$ sudo ./vpncmd 3 check exit /usr/local/vpnserver$ cd ### initスクリプトを書いて、パーミッションを755にしておく ~$ sudo chmod 755 /etc/init.d/vpnserver ~$ sudo cat /etc/init.d/vpnserver #!/bin/sh # description: SoftEther VPN Server DAEMON=/usr/local/vpnserver/vpnserver LOCK=/var/lock/vpnserver test -x $DAEMON || exit 0 case "$1" in start) $DAEMON start touch $LOCK ;; stop) $DAEMON stop rm $LOCK ;; restart) $DAEMON stop sleep 3 $DAEMON start ;; *) echo "Usage: $0 {start|stop|restart}" exit 1 esac exit 0 ### sysv-rc-confでvpnserverの2,3,4,5にチェック ~$ sudo sysv-rc-conf ... ### 再起動 ~$ sudo reboot
SoftEther VPN Serverのセットアップ
~$ cd /usr/local/vpnserver /usr/local/vpnserver$ sudo ./vpncmd 1 localhost (Enter) ### VPN Serverのパスワードを設定 VPN Server> ServerPasswordSet (パスワードを設定) ### 仮想ハブを作成 ここではVPNHUBという名前にしてある VPN Server> HubCreate Name of Virtual Hub to be created: VPNHUB ### VPNHUBに切り替え VPN Server> Hub VPNHUB ### SecureNatを有効に VPN Server/VPNHUB> SecureNatEnable ### ユーザを作成 ここではvpnという名前にしてある VPN Server/VPNHUB> UserCreate vpn (Enter) (Enter) (Enter) ### 上で作ったvpnユーザのパスワードを設定 VPN Server/VPNHUB> UserPasswordSet vpn (VPN User Password) (VPN User Password) ### IPSecを有効に VPN Server/VPNHUB> IPSecEnable Enable L2TP over IPsec Server Function (yes / no): yes Enable Raw L2TP Server Function (yes / no): no Enable EtherIP / L2TPv3 over IPsec Server Function (yes / no): no Pre Shared Key for IPsec (Recommended: 9 letters at maximum): (IPsec Secret Key) Default Virtual HUB in a case of omitting the HUB on the Username: VPNHUB VPN Server/VPNHUB> exit $ sudo service vpnserver restart
OS X(クライアント)側の設定
システム環境設定
→ネットワーク
→左下の+マークで追加
↑画像のサーバアドレス
とアカウント名
を埋めたら、下にある認証設定…
をクリック
ユーザ認証 パスワード
に(VPN User Password)を、コンピュータ認証 共有シークレット
に(IPsec Key)を入力
これで接続ができるように(┐「ε:)
GitLabでMaven Repositoryを作ってGradleから使う
研究室でJavaのプログラムを書いているのですが、ライブラリを研究室内で共有するときにMaven Repositoryを立てたらいいのではないかと思ったので、GitLab上で使うようにしました。
想定するプログラム
- ライブラリ(MyLib)
- MyLibを使用するプログラム(MyProgram)
MyLibのbuild.gradle
apply plugin: 'idea' apply plugin: 'java' apply plugin: 'maven' def defaultEncoding = 'UTF-8' [compileJava, compileTestJava]*.options*.encoding = defaultEncoding sourceCompatibility = 1.7 targetCompatibility = 1.7 group = 'net.rkmathi' archivesBaseName = 'mylib' version = '1.0.0' repositories { maven { url System.getenv('HOME') + '/.m2/repository' } mavenCentral() } dependencies { testCompile 'junit:junit:4.11' } uploadArchives.repositories.mavenDeployer { repository(url: "file:${projectDir}/maven") }
これでgradle uploadArchives
を実行すると、maven
ディレクトリ以下に出力されます。
- uploadArchivesで出力したら、gitリポジトリにそのディレクトリも追加して、GitLabへプッシュします。
MyProgramのbuild.gradle
apply plugin: 'idea' apply plugin: 'java' apply plugin: 'maven' def defaultEncoding = 'UTF-8' [compileJava, compileTestJava]*.options*.encoding = defaultEncoding sourceCompatibility = 1.7 targetCompatibility = 1.7 group = 'net.rkmathi' archivesBaseName = 'myprogram' version = '1.0.0' repositories { maven { url System.getenv('HOME') + '/.m2/repository' } maven { url 'http://<<GitLab Address>>/<<User Name>>/mylib/raw/master/maven' } mavenCentral() } dependencies { compile 'net.rkmathi:mylib:1.0.0' testCompile 'junit:junit:4.11' } uploadArchives.repositories.mavenDeployer { repository(url: "file:${projectDir}/maven") }
これで、MyProgramからMyLibを使うことができました。
http://<<GitLabのアドレス>>/<<ユーザ名>>/<<プロジェクト名>>/raw/master
をmaven repositoriesの場所に指定することによって、GitLab上にあるライブラリを取得できるようになります。
ワーイ
ISUCON4本戦に参加したけどダメでした
ISUCON4の本戦に@cnosukeさんと@k0kubunさんの3人で、「railsへの執着はもはや煩悩」というチームで出てきました。
cnosukeさんのブログ( http://cnosuke.hatenablog.com/entry/2014/11/11/141321 )やk0kubunさんのブログ( http://k0kubun.hatenablog.com/entry/2014/11/09/003455 )に状況とか詳しく書いてあるのであんまり書くことないですが、とりあえず最終結果は6,000点も行かずにダメダメな感じでした(ヽ´ω`)
戦略
動画広告の配信だし、多分ベンチマークから攻撃のようなアクセスをされるだろうと思い、3台与えられたサーバ全部に対してアクセスさせるようにしました。
ただし、各サーバともメモリが1GBしか積んでいなかったためスワップしまくりだし、OOM Killerにunicornだのvarnishだのを殺されて辛い感じでした。
反省とか
ISUCONの敵であるbenchmarkerの挙動をもっとちゃんと把握しておくべきでした。
benchmarkerからのアクセスログを覗いていたら、"Galaxy Nexus"とかいろんな端末のUAがあって「なんかbenchmarkerめちゃ凝ってるぽいなー」とは思ったのですが、まさかブラウザの挙動を模してキャッシュ機能とかまで備えているとは考えられませんでした。
何か問題があってそれに対して高速化を図るのに、その問題をきちんと把握せずに取り組むのは今更ですが良くなかったです(ヽ´ω`)
(でも競技時間が8時間しかないので焦ってしまう…)
おわり
今年で学生は終わりなので最後の学生枠だったのですが、本戦まで行けて(一応)最終結果をFailせずに終われたのはよかったです。
来年も出場したいので、社会人枠で戦えるようにちゃんと勉強していきます。
ISUCONは競技中はずっと緊張感があり、とても楽しかったです。
運営をしてくださった皆様に感謝しております。本当にありがとうございました。
今年もISUCON4予選に参加した→予選通過できた!
去年のISUCON3にも参加してダメだったんですが、今年はISUCON夏期講習に参加してから、ISUCON4の予選に参加しました。
学生枠「railsへの執着はもはや煩悩の域であり、開発者一同は瞑想したほうがいいと思います。」というチーム名で、id:cnosukeさんとid:k0kubunさんの3人で出場しました。
k0kubunさんの記事→http://k0kubun.hatenablog.com/entry/2014/09/28/202828
学生枠は上位5チームが本戦に出場できるのですが、予選1日目の時点で4位だったので本戦に行けるかはびみょいです(;´Д`)
ちなみに、使った言語はRubyでした。
開発環境まわりでやったこと
各自で自分専用のインスタンスを立ち上げる
m3.xlargeインスタンスが$0.4/1hくらいだったので、各自でインスタンス立ち上げてそこで開発/ベンチマークが終わったら、GitHubにプルリ投げて本番用インスタンスでベンチマーク走らせるってフローでやってました。
GitHub
去年、開発中にベンチとったらいつの間にかFailしまくってしまい、どこで間違えたのかわからなくなってしまったので、今年は GitHubのプライベートリポジトリを立ててそこでプルリ投げ合いながら開発することにしました。
事前に3人で集まって練習した時は1つのインスタンスを3人で触っていたので、どこを変更したのかよくわからなくなってました。 ですが、この方法で割と安全に開発が進められたと思いました。
実装まわりでやったこと
erbをslimに置き換えた
事前にISUCON2の問題を練習していたときに、erbよりもslimのほうが速かったのでそうしました。
'/'はnginxで静的HTMLを返すようにした
トップページもslimで描画でしていたのですが、出力HTMLは変わってなかったのでnginxで生HTMLを返すようにしました。
これと、他の2人がやってくれたvanishやMySQLのインデックスなどでだいたいスコアが8000点前後でした。
反省点
プルリ体制にしてたのに自分がミスった
プルリを投げるときは手元で動かしたベンチマークの結果も一緒に貼って、ちゃんとベンチマークが落ちないことを確かめていたのですが、自分が/reportをいじった時のプルリでベンチマークが落ちていたのにメッセージに気づくことができなかったです。 そのため、後半で「実はベンチマークとおってない」ことに気づいてから、ロールバックするのに時間がかかってしまいました(;´Д`)
ルール(というかツール)をちゃんと把握しておくべきだった
ベンチを走らせるときに、編集したinit.shが何故か適用されていなくて困惑してたら、土壇場になって--help
つけたらちゃんとオプションがあることに気づきました。
また、その時にworkloadについても気づけました(;´Д`)
モッタイナカッタ…
@cnosuke @k0kubun @rkmathi 応援してます
— そらは (@sora_h) September 27, 2014
m(_ _)m
本戦に行けるかどうかはまだ分かってないですが、今年のISUCON予選もとても楽しかったです!!!
10/06追記: 学生枠4位通過で、本戦に参加できることになりました!!!やった!!!
運営をしてくださった方々にはとてもお世話になりました。
ありがとうございました!!!