Loading
- Vagrant + Jenkins の CI を AWS でも回す
- Vagrant + Chef Solo + serverspec + Jenkins でサーバー構築を CI
- require.js 環境で mocha + expect + testem を使った JavaScript テスト
- Backbone.jsガイドブック
- 昨今のWebアプリケーションのひな形その2 - Grunt
昨晩 Jenkins と Vagrant で CI だ、と書いたら
という反応があった。確かに、可能なら物理サーバに依存しない形でテストできるとより嬉しい場面もありそうですね。
しかしそこは Vagrant。Vagrant はバージョン 1.1 から、バックエンドを VirtualBox だけでなく AWS (EC2) などの IaaS を指定して仮想サーバーを作ったり壊したりできるようになっています。詳しくは http://d.hatena.ne.jp/naoya/20130315/1363340698 この辺を。この機能を利用すれば昨日の Jenkins + Vagrant のフローをほとんど変えずに、EC2 のインスタンスでのインテグレーションテストができそうですね。
速見もこみち「では、早速やっていきましょう。」
Multi VM でローカル/リモート両対応に
せっかくなので VirtualBox を使ったローカルでのテストと、EC2 を使ったリモートでのテスト、両方を流せるようにしたい。Vagrant の Multi VM を使えば一つの Vagrantfile で複数の仮想サーバーを管理するのも簡単です。
# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure("2") do |config| config.vm.define :local do |local| local.vm.box = "centos" end config.vm.define :remote do |remote| remote.vm.box = "dummy" remote.vm.provider :aws do |aws, override| aws.access_key_id = ENV[AWS_ACCESS_KEY_ID] aws.secret_access_key = ENV[AWS_SECRET_ACCESS_KEY] aws.keypair_name = "naoyas keys" aws.instance_type = "t1.micro" aws.region = "ap-northeast-1" aws.ami = "ami-a3af2ba2" aws.security_groups = [ webserver ] aws.tags = { Name => jenkins-test } override.ssh.username = "ec2-user" override.ssh.private_key_path = "~/.ssh/aws-naoyaskeys.pem" end end end
こんな感じ。これで
% vagrant up local
とすればいつも通り VirtualBox のサーバーが立ち上がり
% vagrant up remote --provider=aws
で EC2 のインスタンスが立ち上がります。
ここで設定した値はビルドのシェルスクリプトから環境変数として参照できるのでそれをうまく使って
if [ $VM_NAME = remote ]; then vagrant up remote --provider=aws else vagrant up local fi vagrant ssh-config $VM_NAME --host=jenkingrant > vagrant-ssh.conf bundle bundle exec knife solo bootstrap jenkingrant -F vagrant-ssh.conf bundle exec rake ci:setup:rspec spec rm -f vagrant-ssh.conf vagrant destroy $VM_NAME -f
という具合に、変数を見ていずれの VM を起動するかを選択するようにする。
これでビルドの際にパラメータの入力が要求されるようになる。
テストしてみる
ちゃんとうまくいっているでしょうか。実際に Jenkins のコンソールを見てみます。一部抜粋。
[workspace] $ /bin/sh -xe /var/folders/nl/vdz7nrv12zj44kcjg_vmpr1r0000gn/T/hudson5435348702666934788.sh + '[' remote = remote ']' + vagrant up remote --provider=aws Bringing machine 'remote' up with 'aws' provider... [remote] Warning! The AWS provider doesn't support any of the Vagrant high-level network configurations (`config.vm.network`). They will be silently ignored. [remote] Launching an instance with the following settings... [remote] -- Type: t1.micro [remote] -- AMI: ami-a3af2ba2 [remote] -- Region: ap-northeast-1 [remote] -- SSH Port: 22 [remote] -- Keypair: naoya's keys [remote] -- Security Groups: ["webserver"] [remote] Waiting for instance to become "ready"... [remote] Waiting for SSH to become available... [remote] Machine is booted and ready for use! [remote] Rsyncing folder: /Users/naoya/.jenkins/jobs/jenkingrant/workspace/ => /vagrant + vagrant ssh-config remote --host=jenkingrant + bundle Resolving dependencies... Using rake (10.0.4) Using builder (3.2.0)
EC2 インスタンスが立ち上がってその VM が使われているのが分かります。上手に焼けました。あ、最後にオリーブオイルをかけるのをお忘れなく。追いオリーブ。
なお、パラメータ付きビルドは、当然のことながら毎回手動でパラメータを入力するのではなくビルド実行のURLを kick する際にパラメータを指定したりといったことが可能です。そのあたりは Jenkinsで外部パラメータで与えたブランチを対象にビルドできるようにしておくと凄惨性あがって墓ドル - ( ???) ゆるよろ日記 などを参照。
蛇足ですが、このテストは落ちた際になぜテストが落ちたかを対象サーバを見て検証できるように、サーバーの破棄はしないようにしています。VirtualBox なら問題ないですが、EC2 だとテストが失敗したときそのままインスタンスが立ち上がりっぱなしだと課金が発生してしまうので、ビルド後に必ず vagrant destroy が走るようにする方がいいかもしれません。
この辺、自分は今のところ Jenkins Notifier for Mac OS X を入れて通知を出して放置しないように気をつけることで回避しております。
もこみち「今日もおいしくできましたね!」
Vagrant + VirtualBoxでやるのすごい楽なんだけど物理マシンが必要なんよな。となると外部IaaSをAPI呼び出しの形が良いのかなとか。 / “Vagrant + Chef Solo + serverspec + J…” URL
2013-05-21 09:44:50 via Hatena
Multi VM でローカル/リモート両対応に
せっかくなので VirtualBox を使ったローカルでのテストと、EC2 を使ったリモートでのテスト、両方を流せるようにしたい。Vagrant の Multi VM を使えば一つの Vagrantfile で複数の仮想サーバーを管理するのも簡単です。
# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure("2") do |config| config.vm.define :local do |local| local.vm.box = "centos" end config.vm.define :remote do |remote| remote.vm.box = "dummy" remote.vm.provider :aws do |aws, override| aws.access_key_id = ENV[AWS_ACCESS_KEY_ID] aws.secret_access_key = ENV[AWS_SECRET_ACCESS_KEY] aws.keypair_name = "naoyas keys" aws.instance_type = "t1.micro" aws.region = "ap-northeast-1" aws.ami = "ami-a3af2ba2" aws.security_groups = [ webserver ] aws.tags = { Name => jenkins-test } override.ssh.username = "ec2-user" override.ssh.private_key_path = "~/.ssh/aws-naoyaskeys.pem" end end end
こんな感じ。これで
% vagrant up local
とすればいつも通り VirtualBox のサーバーが立ち上がり
% vagrant up remote --provider=aws
で EC2 のインスタンスが立ち上がります。vagrant up で両方同時に立ち上げることも可能です。
Jenkins のパラメータ付きビルドで振り分ける
Jenkins には「ビルドのパラメータ化」なる設定項目があって、これを使うとユーザーからの入力値に応じてビルドの内容を動的に変更したりすることができる。例えば、Git から取ってくるブランチ名をビルドの毎に指定したりとかにも使われる。これを使って先の local / remote の VM 名を選択できるようにします。
ここで設定した値はビルドのシェルスクリプトから環境変数として参照できるのでそれをうまく使って
if [ $VM_NAME = remote ]; then vagrant up remote --provider=aws else vagrant up local fi vagrant ssh-config $VM_NAME --host=jenkingrant > vagrant-ssh.conf bundle bundle exec knife solo bootstrap jenkingrant -F vagrant-ssh.conf bundle exec rake ci:setup:rspec spec rm -f vagrant-ssh.conf vagrant destroy $VM_NAME -f
という具合に、変数を見ていずれの VM を起動するかを選択するようにする。
これでビルドの際にパラメータの入力が要求されるようになる。
テストしてみる
ちゃんとうまくいっているでしょうか。実際に Jenkins のコンソールを見てみます。一部抜粋。
[workspace] $ /bin/sh -xe /var/folders/nl/vdz7nrv12zj44kcjg_vmpr1r0000gn/T/hudson5435348702666934788.sh + '[' remote = remote ']' + vagrant up remote --provider=aws Bringing machine 'remote' up with 'aws' provider... [remote] Warning! The AWS provider doesn't support any of the Vagrant high-level network configurations (`config.vm.network`). They will be silently ignored. [remote] Launching an instance with the following settings... [remote] -- Type: t1.micro [remote] -- AMI: ami-a3af2ba2 [remote] -- Region: ap-northeast-1 [remote] -- SSH Port: 22 [remote] -- Keypair: naoya's keys [remote] -- Security Groups: ["webserver"] [remote] Waiting for instance to become "ready"... [remote] Waiting for SSH to become available... [remote] Machine is booted and ready for use! [remote] Rsyncing folder: /Users/naoya/.jenkins/jobs/jenkingrant/workspace/ => /vagrant + vagrant ssh-config remote --host=jenkingrant + bundle Resolving dependencies... Using rake (10.0.4) Using builder (3.2.0)
EC2 インスタンスが立ち上がってその VM が使われているのが分かります。上手に焼けました。あ、最後にオリーブオイルをかけるのをお忘れなく。追いオリーブ。
なお、パラメータ付きビルドは、当然のことながら毎回手動でパラメータを入力するのではなくビルド実行のURLを kick する際にパラメータを指定したりといったことが可能です。そのあたりは Jenkinsで外部パラメータで与えたブランチを対象にビルドできるようにしておくと凄惨性あがって墓ドル - ( ???) ゆるよろ日記 などを参照。
蛇足ですが、このテストは落ちた際になぜテストが落ちたかを対象サーバを見て検証できるように、サーバーの破棄はしないようにしています。VirtualBox なら問題ないですが、EC2 だとテストが失敗したときそのままインスタンスが立ち上がりっぱなしだと課金が発生してしまうので、ビルド後に必ず vagrant destroy が走るようにする方がいいかもしれません。
この辺、自分は今のところ Jenkins Notifier for Mac OS X を入れて通知を出して放置しないように気をつけることで回避しております。
もこみち「今日もおいしくできましたね!」
Jenkins おじさんと戯れること半日、うまくいったので備忘録を残しておく。
やりたかったのは Chef で構築したサーバーを Jenkins で CI する、というもの。このときサーバーはテストが終わる度に破棄して、テスト開始時に再度真っ新な状態から立ち上げたい。(こういうサーバーを壊して作ってというテストはなんという名前で呼ばれるのだろう?)
* 仮想サーバーを破棄/作成をプログラマブルにやるのはもちろん Vagrant
* プロビジョニングは Chef
* Chef の環境を整えるのに knife-solo 0.3.0.pre3
* テストは serverspec
* コードは Github に上げる (https://github.com/naoya/jenkins-vagrant-test)
* CI は Jenkins
という構成になっている。ひとまず Jenkins や Vagrant はローカルの OSX に入れている。
レポジトリ
プロビジョニング用レポジトリのディレクトリは、knife solo で作ったもの。Vagrantfile もその中に入れる。
% bundle exec knife solo init jenkins-vagrant-test % cd jenkins-vagrant-test % vagrant init centos
この辺は Chef Solo + Vagrant を使う際のいつもの様子。
Gemfile
レポジトリ内に Gemfile を作って CI に必要な gem を列挙しておく。
source https://rubygems.org gem "knife-solo", "~> 0.3.0.pre3" gem serverspec gem rake gem ci_reporter
RSpec の出力を Jenkins 用にカスタマイズするため ci_reporter も入れておこう。
Serverspec でテストを書いてレシピを書く。
特に変わったことはしていない。いつも通り
* serverspec は https://github.com/mizzy/serverspec のドキュメントなどを
* Chef Solo は 入門Chef Solo - Infrastructure as Code この辺がオススメですコポォ
ci_reporter で出力を出すためには serverspec の Rakefile に
require ci/reporter/rake/rspec
を追記しとく。これで
これでビルドを実行すると Github のコードが pull されて、vagrant up で仮想サーバーが立ち上がり、Bundler で必要な gem が入って、knife solo でノードの Chef が最新になり、クックブックが適用されたノードに serverspec が走り、テストに問題なければサーバーが破棄される。この一連のテストは仮想サーバーの上げ下げそのほかがあって時間がかかるので CI に任せるのにうってつけ。
普段クックブックを調整してその場でテストを回すのにはあらかじめ仮想サーバーを立てておいたものに、差分のクックブックを適用して serverspec でテスト、サーバーは破棄しない・・・みたいなのを Guard や Grunt なんかを使ってテスト保存のたびカジュアルに回す。一方、調整が終わったクックブックは他のクックブック含め真っ新なサーバーに適用してがっちりインテグレーションテストしてみないと予想外のことが起こるかもしれない。そのための CI をしたかった、というわけでした。
サーバー構築も継続的インテグレーションする時代です。
rake ci:setup:rspec spec で Jenkins が読める XML でのレポートが、テスト実行時に spec/reports/*.xml に吐き出される。
SSH 周りをどうするか
細かい話なのだけど、この構成で CI を回そうとするときに問題になるのが SSH 周りの設定。knife-solo も serverspec も Vagrant で立ち上げた仮想サーバーに ssh してごにょごにょするツールですが全部自動化しようとすると vagrant up したサーバーをどうやって名前解決するかというところで困る。
幸い
* Vagrant には vagrant ssh-config で ssh 設定を出力するオプションがあり
* knife solo には ssh 設定ファイルを指定するオプション (-F) があり
* serverspec は serverspec 本体を弄らなくても Net::SSH のオプションを触れる構成になっている
ので、vagrant ssh-config の出力をファイルに吐き出してそれを両者に読み込ませるようにすれば、課題は解決できる。つまり、Jenkins のビルド設定のシェルスクリプトは以下になる。
# 仮想サーバー起動 vagrant up # ssh 設定を出力 vagrant ssh-config --host=jenkingrant > vagrant-ssh.conf # gem 入れる bundle # bootstrap = prepare + cook : Chef入れてクックブック適用 bundle exec knife solo bootstrap jenkingrant -F vagrant-ssh.conf # serverspec でテスト実行 bundle exec rake ci:setup:rspec spec # 設定ファイル削除 rm -f vagrant-ssh.conf # 仮想サーバー破棄 vagrant destroy -f
serverspec の ssh 設定周りは spec/spec_helper.rb で Net::SSH::Config.for を読んでるところを
options = Net::SSH::Config.for(c.host, files=["vagrant-ssh.conf"])
とする。これで引数に与えたファイルも ssh の設定として使われる。
Jenkins 周りの設定
Git プラグインを入れて、ビルドの際に github からコードを取ってくるように設定し、ビルドの設定に先のシェルを入れている。特に変わったことはしていない。
これでビルドを実行すると Github のコードが pull されて、vagrant up で仮想サーバーが立ち上がり、Bundler で必要な gem が入って、knife solo でノードの Chef が最新になり、クックブックが適用されたノードに serverspec が走り、テストに問題なければサーバーが破棄される。この一連のテストは仮想サーバーの上げ下げそのほかがあって時間がかかるので CI に任せるのにうってつけ。
普段クックブックを調整してその場でテストを回すのにはあらかじめ仮想サーバーを立てておいたものに、差分のクックブックを適用して serverspec でテスト、サーバーは破棄しない・・・みたいなのを Guard や Grunt なんかを使ってテスト保存のたびカジュアルに回す。一方、調整が終わったクックブックは他のクックブック含め真っ新なサーバーに適用してがっちりインテグレーションテストしてみないと予想外のことが起こるかもしれない。そのための CI をしたかった、というわけでした。
サーバー構築も継続的インテグレーションする時代です。
先日書いた自分用アプリケーションのひな形
* http://d.hatena.ne.jp/naoya/20130503/1367581629
* http://d.hatena.ne.jp/naoya/20130504/1367640512
これに、JavaScript のテスト環境も追加したい。
結論からいくと、フレームワークには mocha + expect、ランナーは testem を使うことにした。ついでにテストダブルライブラリとして Sinon.js も有効にした。
ちなみに今回の文脈は End to End のテストではなくてユニットテスト周りのおはなしです。
Mocha + Expect
JavaScript のこの辺のテスト周りは今もいろいろなツールの整備が進んでいて、今回採用した以外にも Jasmin や QUnit そのほか色んな物がある。昨今の状況に関しては 先日の HTML5とか勉強会のレポート が詳しい。
今回 mocha + expect.js を使うことにしたのは、@teppeis さんが WEB+DB PRESS の Vol.73 号で、あと @hokaccha さんが神プレゼンの中でおすすめしていたのが主な動機。モック、アサーション、フレームワークが全部入りな Jasmin も良いけど、それぞれ個別に選択するほうが好み、つまり agnostic なほうが良い場合は mocha をベースにするのが良さそうだと思った。
実際 mocha + expect.js を使ったテストはどんな感じになるかといえば
define -> describe 'テスト', -> it 'が可能であること', -> expect(true).to.be true it 'でspyが使えること', -> spy = sinon.spy() spy() expect(spy.calledOnce).to.ok()
こんな感じです。これは mocha の BDD スタイル。
Testem
これを testem という、Node.js ベースのリモートランナーで実行する。testem を使うと、mocha + expect で書いたテストを複数のブラウザをまとめて立ち上げてテストさせることができる。操作にあたって面倒な手続きはなく、コマンドラインから testem を実行するだけ。testem が Chrome や Firefox をまとめて立ち上げてテストしてくれるし、ローカルファイルの更新を検知して socket.io 経由でテストを再実行してブラウザ上の結果を動的に更新してくれたりする。
testem の出力は CI 向けに調整することもできて、Jenkins はもちろん、Travis CI でも継続的インテグレーションさせることができるそうです。
JavaScript のブラウザ上でのテスト、というとだいぶ前に Selenium を使って、結構面倒だなあという印象を持っていたけど、ずいぶんと洗練されたものだなあと思いました。
Require.js といっしょに使う
ところでこの mocha + expect + testem を、アプリケーションもテストも require.js を使っているという前提で動かそうとして結構はまった。試行錯誤の上落ち着いた結果を今のレポジトリに反映してあります。
* https://github.com/naoya/boilerplate
コツとしては、普通は testem がブラウザでテストを実行するようの html を吐いてくれるところ、それを testem 任せにしないで自分で書く点、require.js の config で baseUrl 含めうまく設定を調整する点・・・ですかね。どの言語でもそうですが、慣れないとこういうライブラリパス周りは本当によく嵌る。
html を自分で書かないと、testem が自動生成するファイルでは mocha を走らせるタイミングやスコープが微妙で、require.js でロードしたライブラリのテストがうまく実行できない。結局 slim でこういうランナー向け html を書いて
DOCTYPE html html head title Test'em link rel="stylesheet" href="/testem/mocha.css" script src="/public/js/vendor/mocha/mocha.js" script src="/public/js/vendor/expect/expect.js" script src="/public/js/vendor/sinon/index.js" script mocha.setup('bdd') script src="/testem.js" / data-main は require.config を共有するために読み込む script src="/public/js/vendor/requirejs/require.js" data-main="/public/js/main" body #mocha javascript: var files = []; {{#serve_files}}files.push("../{{src}}");{{/serve_files}} require(files, function() { mocha.run(); });
testem の設定は以下にすることで、問題を回避。
framework: mocha test_page: test/runner.html launch_in_dev: - Chrome src_files: - test/**/*.js
HTML の中から mustache (かな?) でテンプレート変数を参照しているところがポイントですね。というか、これも @teppeis さんに教えてもらったのですけど。
後は細かいバッドノウハウとして Sinon.js が bower で拾ってくるとブラウザ用にビルドされてなくて嫌んな感じだったので bower.json は
{ "name": "My Application", "version": "0.0.1", "dependencies": { "requirejs": "latest", "jquery": "latest", "backbone-amd": "latest", "underscore-amd" : "lastest" }, "devDependencies": { "mocha": "latest", "expect": "latest", "sinon": "http://sinonjs.org/releases/sinon-1.7.1.js" } }
と URL を直打ち。bower は bower レポジトリにないスクリプトもこの方法でいちおう管理することができるようになっている。
これで、テストを test ディレクトリ以下に追加すると、Grunt で自動でコンパイルしたのを testem が走らせるという流れになる。TDD の環境が完成しました。
Grunt を使う
テスト周りの環境を整えたら、CoffeeScript のファイル数が増えたりして Sinatra 任せのコンパイルではちょっとワークフロー的に難しくなってきたので、Coffee のコンパイルはもう Grunt に任せるようにした。
* https://github.com/naoya/boilerplate/blob/master/Gruntfile.coffee
ここに Gruntfile があるので似たようなことをしたい、という方の参考にでもなれば幸い。
こんな感じでテストの環境も自分的にはまあモダンな感じで取りそろえることができたし、結構満足している。いや、ここで満足しないでアプリケーション書きましょうという話ですね。はい。ウッ、Backbone.View のテスト難しい・・・地球のみんな、オラに js 力を・・・。
そうそう、途中さらっと流しましたが @hokaccha さんの http://hokaccha.github.io/slides/javascript_design_and_test/ このプレゼン資料は大変に良資料ですので激しくオススメしておきます。WEB+DB Vol.73 にもこの辺のツールのことが詳しく書かれておりますことですよ。
testem の出力は CI 向けに調整することもできて、Jenkins はもちろん、Travis CI でも継続的インテグレーションさせることができるそうです。
JavaScript のブラウザ上でのテスト、というとだいぶ前に Selenium を使って、結構面倒だなあという印象を持っていたけど、ずいぶんと洗練されたものだなあと思いました。
Require.js といっしょに使う
ところでこの mocha + expect + testem を、アプリケーションもテストも require.js を使っているという前提で動かそうとして結構はまった。試行錯誤の上落ち着いた結果を今のレポジトリに反映してあります。
* https://github.com/naoya/boilerplate
コツとしては、普通は testem がブラウザでテストを実行するようの html を吐いてくれるところ、それを testem 任せにしないで自分で書く点、require.js の config で baseUrl 含めうまく設定を調整する点・・・ですかね。どの言語でもそうですが、慣れないとこういうライブラリパス周りは本当によく嵌る。
html を自分で書かないと、testem が自動生成するファイルでは mocha を走らせるタイミングやスコープが微妙で、require.js でロードしたライブラリのテストがうまく実行できない。結局 slim でこういうランナー向け html を書いて
DOCTYPE html html head title Test'em link rel="stylesheet" href="/testem/mocha.css" script src="/public/js/vendor/mocha/mocha.js" script src="/public/js/vendor/expect/expect.js" script src="/public/js/vendor/sinon/index.js" script mocha.setup('bdd') script src="/testem.js" / data-main は require.config を共有するために読み込む script src="/public/js/vendor/requirejs/require.js" data-main="/public/js/main" body #mocha javascript: var files = []; {{#serve_files}}files.push("../{{src}}");{{/serve_files}} require(files, function() { mocha.run(); });
testem の設定は以下にすることで、問題を回避。
framework: mocha test_page: test/runner.html launch_in_dev: - Chrome src_files: - test/**/*.js
HTML の中から mustache (かな?) でテンプレート変数を参照しているところがポイントですね。というか、これも @teppeis さんに教えてもらったのですけど。
後は細かいバッドノウハウとして Sinon.js が bower で拾ってくるとブラウザ用にビルドされてなくて嫌んな感じだったので bower.json は
{ "name": "My Application", "version": "0.0.1", "dependencies": { "requirejs": "latest", "jquery": "latest", "backbone-amd": "latest", "underscore-amd" : "lastest" }, "devDependencies": { "mocha": "latest", "expect": "latest", "sinon": "http://sinonjs.org/releases/sinon-1.7.1.js" } }
と URL を直打ち。bower は bower レポジトリにないスクリプトもこの方法でいちおう管理することができるようになっている。
これで、テストを test ディレクトリ以下に追加すると、Grunt で自動でコンパイルしたのを testem が走らせるという流れになる。TDD の環境が完成しました。
Grunt を使う
テスト周りの環境を整えたら、CoffeeScript のファイル数が増えたりして Sinatra 任せのコンパイルではちょっとワークフロー的に難しくなってきたので、Coffee のコンパイルはもう Grunt に任せるようにした。
* https://github.com/naoya/boilerplate/blob/master/Gruntfile.coffee
ここに Gruntfile があるので似たようなことをしたい、という方の参考にでもなれば幸い。
こんな感じでテストの環境も自分的にはまあモダンな感じで取りそろえることができたし、結構満足している。いや、ここで満足しないでアプリケーション書きましょうという話ですね。はい。ウッ、Backbone.View のテスト難しい・・・地球のみんな、オラに js 力を・・・。
そうそう、途中さらっと流しましたが @hokaccha さんの http://hokaccha.github.io/slides/javascript_design_and_test/ このプレゼン資料は大変に良資料ですので激しくオススメしておきます。WEB+DB Vol.73 にもこの辺のツールのことが詳しく書かれておりますことですよ。
WEB+DB PRESS Vol.73
posted with amazlet at 13.05.09
設樂 洋爾 白土 慧 奥野 幹也 佐藤 鉄平 後藤 秀宣 mala 中島 聡 堤 智代 森田 創 A-Listers はまちや2 大和田 純 松田 明 後藤 大輔 ひろせ まさあき 小林 篤 近藤 宇智朗 まかまか般若波羅蜜 Mr. O
技術評論社
売り上げランキング: 28,155
Backbone.js ガイドブックを一通り読みました。言及するか少し迷ったけど、まだあまり話題になっていないようなので書いておこうと思います。
Backbone.js あるいはこれによく似たようなフレームワークは今後、Webアプリケーション開発でよく使う道具になっていくと思う。というか、すでになっているでしょう。
Backbone.js は「クライアントサイドMVCフレームワーク」と呼ぶと良くわからない。クライアントサイドMVCフレームワークが注目される以前から、ある程度以上の規模の JavaScript アプリケーションになるとちゃんとしてるものは構造化が行われていた。イベントを集約するオブジェクトを作って、ドメインロジックに相当する部分は切り分けて、画面の更新はイベントを発行して pub/sub モデルで書く・・・ 腕の良い JavaScript プログラマが書いた JavaScript アプリケーションはだいたいそんな感じになっている。
「へえ、こんな風に書くんだねえ」と思いながらも、よしじゃあ自分も・・・といざやってみると、なんだかうまくいかない。How To だけ知っていて、構造化されたアプリケーションモデルが頭の中にちゃんと描けていないから。これじゃいかんなあ、と「モダンJavaScript」的ないろんな本を読んでみるんだけど、prototype チェインはこうなっているとか new を使うなとかそういうことは延々と書いているのに、肝心のアプリケーション構造化についてきちんと触れているものはなかなか無い。「大きくなったときに破綻するよねえ」と分かりながらも jQuery のイベントハンドラをべたに書いていく・・・。
そういう人に「あなたもこれを使えばできますよ」という指針と実装を与えてくれるのが Backbone.js だと思う。
Backbone.js を使うと綺麗に構造化された JavaScript アプリケーションを書くことができる。ただ、昨今のWebアプリケーションフレームワークなんかと違って、さすがに JavaScript のグローバルスコープを押さえ込んで "Backbone Way" を強制するほどの強い制約は、このフレームワークにはない。例えば、Backbone.js の流儀に従わずにモデルクラスの中で jQuery で責務範囲外の DOM を操作してしまったら設計は破綻するけどそれを不可能にする制約はどこにもない。だからそこはフレームワークに身をゆだねるのではなくちゃんと意識して Backbone の流儀に従ってコードを書かなければならない。
でも、公式の文書含め、Backbone.js のチュートリアルや入門記事なんかを幾つか読んでみたけれども、その Backbone.js の流儀的に「こういう場合はこういう風に書く」とか逆にこれはアンチパターンだという風に説明しているものはあまり見なかった。Backbone.js で TODD アプリケーションを作って、Backbone で書くとこうなります・・・止まりだ。肝心の部分が結構な暗黙知になってしまっている。
Backbone.js ガイドブックにはその Backbone.js の流儀がちゃんと解説されている。これがとにかく良かった。
Backbone の MVC では V に View と Controller が内包されていて、View に実装されるメソッドは Controller メソッドと View メソッドという二つに種類に分類することができる。そして Controller メソッドで DOM のイベントを Subscribe し、そのイベントを契機に Model を操作する。画面の更新は、その Model の操作に伴って発火されたイベントを契機にした View メソッドとして実装すべきである・・・ つまり 「DOM → イベント → Controller メソッド → Model → イベント → View メソッド → 画面再描画」という流れになるように書くべき。既存の Backbone アプリケーションを読むときも、そこに着目すれば処理の流れを逐次で追えるので分かりやすい・・・ など、こんなことが書かれている。
というわけで、"自分にとっては" 良著だった。ちょうど今作っている Webアプリケーションに、苦手な JavaScript での実装が必要になって、いいタイミングでこの本が手元に届いたのであった。*1
けど "自分にとっては" とそこは断りをいれておこう。少々、書籍の構成に難がある。端的に言うと、この書籍は「Backbone.js ガイドブック」であり「Backbone.js 入門」ではない。Backbone.js の本質的な考え方、ベストプラクティスは語られている一方、Backbone がどんなものか分からない初学者に向けた構成にはなっていない。Backbone.Model、Backbone.Collection、Backbone.View と Backbone を構成するクラス群の割と詳細な解説が続いたあとようやく TODO アプリケーションの解説、みたいな構成になっていて、おそらく、Backbone.js をある程度知らないと途中でめげる。実際自分は Backbone.Router や Backbone.sync を多用するアプリケーションは書いたことがないので、その辺の章は前提知識が欠けているために負荷が高く感じた・・・ようするに消化仕切れなかった。あと索引がほどんど役に立たないとか、require.js とか Grunt の解説は若干中途半端感があるし、ほんとに必要だったのかとか少々ツッコミたくなる部分もある。
ま、Amazon の商品説明を見ると、そもそも初学者は対象にしていないのかもしれない、と思う。もし Backbone.js を一通り触ってみたけど、Backbone を使ったときの実装にまだ迷いが多い・・・という自分のような人は手に取ってみるとよい本だと思います。
まずは Backbone.js 入門から・・・という人は ドットインストールの Backbone.js 入門 あたりがいいんじゃないかな。これもいきなり「では Model を作っていきましょう」とかで入っていくので最初は戸惑うけど、一通り作業を目で追えばだいたいどんなものか、が分かるとは思います。ただし、解説男性の男前の声を延々聞き続ける苦行に耐えられるなら、ですけども。
*1:これは献本ではなく自分で購入した本です。わざわざこんなことを表明しないといけない昨今にポイズン
昨日の続き。
こういうアプリケーションのテンプレートを管理するのに便利な仕組みはないですかねーと言っていたら @teppeis さんや @omo2009 さんに Grunt や Yeoman はどうかと教えてもらった。
Grunt はユースケースとしては JavaScript の連結や圧縮、SCSS/LESS なんかのメタ言語のコンパイルをするときに使うもの、つまり rake なんかと同じようなものと以前にチラ見した程度で知った気になっていたけども、ちょっと違っていた。Grunt は確かにタスクランナーではあるのだが、Node.js で実装している利点を十分に活かして、任意のファイルが更新されたのをトリガに一連のタスクを実行させたり、Grunt で Webサーバーを立ち上げて他のタスクと連携させたりといったことができるようになっている。プラグインの仕組みがあって、エコシステム的に結構活発に開発されているみたいだ。
Yeoman は、まだあまり調べられていないけど bower と Grunt を組み合わせて、各種ファイルのコンパイルやテストやそのランナーなどを含めて管理するインテグレーションキットみたいなものだと思う。
Grunt
Grunt の典型的なユースケースとしては、JavaScript ファイルの更新を検知してそれらを連結したり minify したりして指定したファイルとして吐き出す、というもの。
Gruntfile という、make/rake でいうところの Makefile/Rakefile みたいなものにタスクを定義する。そして grunt コマンドでタスクを実行する。Gruntfile は node.js よろしくなツールなので javascript で書くわけだけど、最近のバージョンでは coffee でいけるようになったので、Gruntfile.coffee として書く。
以下は
* sass ファイルを更新すると css にコンパイルして連結
* その css を minifty して app.min.css に出力
* coffee ファイルを更新すると js にコンパイル & 連結
* その js を uglify で圧縮して出力
ということをやっている。regarde というプラグインが、ファイル更新を監視するためのもの。
module.exports = (grunt) -> grunt.initConfig sass: dist: files: 'css/app.css': ['sass/base.sass', 'sass/styles.sass'] cssmin: compress: files: 'css/app.min.css': 'app.css' coffee: compile: files: 'js/app.js': ['coffee/base.coffee', 'coffee/application.coffee'] uglify: my_target: options: mangle: true files: 'js/app.min.js': ['js/app.js'] regarde: css: files: 'sass/*.sass' tasks: ['sass', 'cssmin'] js: files: 'coffee/*.coffee' tasks: ['coffee', 'uglify'] grunt.loadNpmTasks 'grunt-regarde' grunt.loadNpmTasks 'grunt-contrib-sass' grunt.loadNpmTasks 'grunt-contrib-cssmin' grunt.loadNpmTasks 'grunt-contrib-coffee' grunt.loadNpmTasks 'grunt-contrib-uglify' grunt.registerTask 'default', [ 'sass', 'cssmin', 'coffee', 'uglify', 'regarde' ]
これで grunt を実行すると
% grunt Running "sass:dist" (sass) task Running "cssmin:compress" (cssmin) task File css/app.min.css created. Running "coffee:compile" (coffee) task File js/app.js created. Running "uglify:my_target" (uglify) task File "js/app.min.js" created. Running "regarde" task Watching sass/*.sass Watching coffee/*.coffee
こんな感じで各タスクが走り出す。regrde によって、sass や coffee を更新すると関連するファイルがすべて更新される。
と、ここまではファイル関してしてタスクを走らせるだけという感じだけど、そのほか各種プラグインを使うとファイルが更新されたらブラウザを自動でリロードするなんて連携ができたり、Jasmine や QUnit でテストを走らせたりといったことも可能になる。Grunt はこんな感じでタスクの実行系を中心として、メタ言語系なんかを実際にコンパイルするタスクの実装がプラグインとして用意されていて、かつリアルタイムに諸々を実行・管理できるタスクランナー・・・といったところだと思う。
で、そのプラグインの中に grunt-init というものがあって、これを使うと Grunt を使ったプロジェクトのひな形を生成できる、というものの様子。本来は grunt-init が目的のものなのかもしれないが、そこまではまだ手を動かせてない。
Grunt で Livereload
てなわけで、昨日作ったひな形にブラウザの livereload の仕組みがあれば便利だなと思ったので Grunt 周りも設定しておいた。
path = require 'path' module.exports = (grunt) -> grunt.initConfig livereload: port: 35729 regarde: views: files: 'views/*.*' tasks: ['livereload'] grunt.loadNpmTasks 'grunt-regarde' grunt.loadNpmTasks 'grunt-contrib-livereload' grunt.registerTask 'default', [ 'livereload-start', 'regarde' ]
これで Grunt を立ち上げると views 以下のファイルの更新に合わせて livereload が機能する。Google Chrome なら LiveReload 拡張 を入れると、35729 ポートで通信してよしなに面倒みてくれる。livereload の仕組みっぽいものは http://aligach.net/diary/20110925.html あたりを参照のこと。
こうなったら sass や slim や coffee のコンパイルも Sinatra 任せにせずに Grunt でやればいい気もする。その辺は必要に応じて追々対応するとする。
Foreman
ところで Grunt を導入したのは良いのだけど grunt と Sinatra な Rack を両方立ち上げるのがめんどくさいので、ここは foreman の出番。Procfile に
application: bundle exec ruby app.rb grunt: grunt
と書いて
% foreman start
で、両方が起動する。
良い感じであります。
引き続き Grunt のライフチェンジングなプラグインがないかと、Yeoman 辺りを調べていこうかなと思います。こうやって開発環境周りのことを書いてるとまたT氏に「中年の危機」とかいって dis られるのでしょうが、気にしない。
良い感じであります。
引き続き Grunt のライフチェンジングなプラグインがないかと、Yeoman 辺りを調べていこうかなと思います。こうやって開発環境周りのことを書いてるとまたT氏に「中年の危機」とかいって dis られるのでしょうが、気にしない。 

