ちゃんとテスト書き始めた話

テストのモチベ=怖いからやる

  • 正直に白状すると、「これまでテスト書いたことない && 会社にテストの文化がない && テスト書いてる時間ない」っていう状況で、時間を割いてでもテストを書こうっていうモチベがなかなか湧かなかった。
  • そんななか、唯一、ハッキリとしたわかりやすいモチベは「デグレが怖い」という恐怖心から解放されることだった。
  • プロジェクトが大きくなるほど、自分が書いたコードがどこまで影響するか把握できなくなってくる。だからといって、変更のたびにブラウザでポチポチ一個ずつ確認する作業はだるい。
  • テストが通ってるという事実が抜群の安心感をもたらすことがわかってきて、ちゃんとテストを書くようになったというお話です。

500返ってないか怖い

  • 一番わかりやすいテスト項目として「ユーザーにエラー画面を表示していないか」というのがまずアタマに浮かんだ。
  • モデルとか変更すると、影響範囲よくわからないし、怖い。
  • 手っ取り早く全部のアクションで500返ってないかテストする方法を考えてみた。
  • response.should be_success的なレスポンスをチェックするテストにタグをつけて、全コントローラーをまたいでレスポンスをチェックするテストだけを実行するってやり方を考えてみた。全コントローラーのテストは時間かかって、たぶん手元では試さなくなりそうだから。
# spec/spec_helper.rb

RSpec.configure do |config|
  config.treat_symbols_as_metadata_keys_with_true_values = true
end
  • RSpec 2.xだとこの設定が必要らしい。
  • it 'hogehoge', status: trueみたいなのをit 'hogehoge', :statusで書けるようになる。
# lib/tasks/spec/status.rake

require 'rspec/core/rake_task'

namespace :spec do
  namespace :controller do
    RSpec::Core::RakeTask.new(:status) do |spec|
      spec.pattern = 'spec/controllers/**/*_spec.rb'
      spec.rspec_opts = '--tag status'
    end
  end
end
  • 自分でrake spec:controllers的なテストのraketaskを定義したいときはRSpec::Core::RakeTask.new(:hoge)使えばいいっぽい。
  • 全コントローラーのテストで、statusってタグがついてるものだけ実行したいので、こんな感じ。
it 'returns successfully', :status do
  get :index
  response.should be_success
end
$ rake spec:controllers:status
  • これでコントローラーをまたいでstatusタグのついたテストだけ実行できる。
  • 全コントローラーのテストは重すぎるので、これでかなり気軽にチェックできるようになると思う。
  • とりあえずステータスコードをチェックするようなテストにstatusタグをつけておく、っていうルールを共有する必要はある。

スクリプトでDBを更新するの怖い

  • ちょっと前に顧客のデータぜんぶ消しちゃった事件があったような気がする。
  • ああいうのあるし、DBを更新する系のスクリプトはちゃんとテストしたい。
  • 仕事ではrails rでスクリプトを実行するんだけど、こういうのはどうやってテストすればいいのかわからなかったので調べたり試行錯誤した。
# config/environments/test.rb

NaotySample::Application.configure do
  $LOAD_PATH.unshift "#{Rails.root}/script"
end
  • script/以下をrequireするためにパスに追加しておく
# spec/scripts/create_naoty_spec.rb

require 'spec_helper'
require 'create_naoty'

describe CreateNaoty do
  it 'creates naoty' do
    CreateNaoty.run
    User.where(name: 'naoty').first.should be_present
  end
end
  • スクリプトを読み込んでテスト内で実行する。
  • モジュールのテストとたぶん同じやり方だと思う。やったことないけど。
# script/create_naoty.rb

module CreateNaoty
  def self.run
    User.create(name: 'naoty')
  end
end

CreateNaoty.run if __FILE__ == $0
  • 処理の中身をモジュールにまとめておいて、テスト内で実行しやすくしておく。
  • if __FILE__ == $0requireされたときにスクリプトが実行されるのを回避してる。
# Guardfile

guard 'rspec' do
  watch(%r{^script/(.+)\.rb$}) {|m| "spec/scripts/${m[1]}_spec.rb" }
end
  • Guardを使って自動テストをやってるので、スクリプトテスト用の設定を追加しておく。
  • これでスクリプトを変更したときに、それのテストを自動で実行するようになる。

まとめ

  • 「怖いからテストする」というモチベはわかりやすい。
  • 「怖いところをテストする」という方針であれば、「何をテストすべきか」を自ずと意識するようになる。
  • rspec-railsで対応できなければ、rspecの便利機能を駆使して試行錯誤する。RSpec bookにお世話になってる。

参考

The RSpec Book (Professional Ruby Series)

The RSpec Book (Professional Ruby Series)

Rails開発環境 2012夏

5月に「Rails開発環境 2012初夏」という記事を公開してそこそこ好評だったので、最近導入してLife-Changingだったツールを「2012夏」バージョンとして紹介しようと思います。今回紹介するのは以下の3つです。

  • pow + xip.io
  • tmuxinator
  • ctrlp.vim

1. pow + xip.io

pow + xip.ioによって同じネットワーク内にある、iPhoneiPadのような他のデバイスからローカルサーバーに接続できるようになりました。これは、スマホ用サイトやアプリで使うAPIの開発で非常に重宝します。特に、実機でないと確認できないような場面では、pow + xip.ioがないと、ステージング環境にデプロイする必要が出てきて、非常に面倒です。

インストールは、公式ページにあるように以下のコマンドを入力するだけです。

$ curl get.pow.cx | sh

使い方としては、まず、Railsのプロジェクトルートへのシンボリックリンクを.powディレクトリに作ります。

$ cd ~/.pow
$ ln -s ~/workspace/rails/cui-aboutme

すると、これだけでローカルサーバーが起動して、http://cui-aboutme.devでアクセスできます。簡単ですねー。

$ open http://cui-aboutme.dev

同じLANにあるデバイスからは、プライベートIPアドレスを使ってアクセスすることができます。

$ ifconfig
...
        inet 192.168.1.4
...

ifconfig等で調べた結果、上のようになった場合、http://cui-aboutme.192.168.1.4.xip.ioで他のデバイスからもアクセスできます。

f:id:naoty_k:20120808013751j:plain

注意点としては、Lionでは「システム環境設定」→「共有」→「Web共有」を有効にしておく必要があります。これがオフになっててハマりました…><ちなみに、Mountain Lionでは「Web共有」の項目がなくなっていますが、手元では無事に成功しています。

実際にプロジェクトで使っていく中でのTipsをいくつかご紹介します。

powder

powderはpowの操作をカンタンに行うためのコマンドラインツールです。Gemfileからインストールします。

# Gemfile

group :development do
  gem 'powder'
end

シンボリックリンクを.powに作る操作や、サーバーを再起動する操作などをカンタンなコマンドで実行できます。

$ powder link
$ powder restart

詳細は公式ページを参照してください。

pry-remote

pryを使っている方は多いと思いますが、powのサーバーはrails sで起動するわけではないので、普通のやり方ではpryを使うことができません。そこで活躍するのが、pry-remoteです。これもGemfileからインストールします。

# Gemfile

group :development, :test do
  gem 'pry-rails'
  gem 'pry-remote'
end

使い方は、いつものbinding.pryの代わりにbinding.pry_remoteとコードに追加して、実行すると処理が止まります(見た目には分かりにくいけど…)。そこで、

$ pry-remote

と打つと、いつものpryコンソールに入れます。

ちょっと分かりにくいかもしれませんが、公式ページも見てもらって実際に使うと雰囲気がわかるとおもいます。

2. tmuxinator

tmuxinatorは、tmuxで起動するセッションをあらかじめ定義しておいて、コマンド一発で開発環境を起動することができるツールです。gemで配布されているので、bundlerでインストールします。公式ページにしたがって準備します。

$ gem install tmuxinator
$ echo "[[ -s $HOME/.tmuxinator/scripts/tmuxinator ]] && source $HOME/.tmuxinator/scripts/tmuxinator" >> .zshrc
$ source .zshrc

使い方としては、mux new [project name]でテンプレートを作って、起動するセッションを定義していきます。

$ mux new cui-aboutme
# .tmuxinator/cui-aboutme.yml

project_name: cui-aboutme
project_root: ~/workspace/rails/cui-aboutme
tabs:
  - main:
      layout: tiled
      panes:
        - git fetch --prune && git status --short --branch
        - curl http://cui-about.me/users
        - tig
  - vim: vi
  - app:
      layout: even-horizontal
      panes:
        - rails c
        - tail -f log/development.log
  - test: guard
  • 各項目で、起動時に実行するコマンドを定義しています。
  • tabsで起動するタブ毎の設定を定義します。上の設定例だと、「main」「vim」「app」「test」の4つのタブを起動します。
  • panesでタブ内で分割するペインを定義し、layoutでペインの配置を定義します。上の設定例だと、「main」タブに「git fetch等gitの操作」「curl等シェルの操作」「tigでコミットログのビューワー」の3つのペインを起動します。

その他、いろいろな設定ができるようなので詳しくは公式ページをご覧ください。

3. ctrlp.vim

ctrlp.vimは、Ctrl-pで起動するファイラーです。unite.vimと近いのかもしれませんが、僕はこっちの方がサクサクしてて操作もわかりやすくて好きです。下はスクリーンショットです。

f:id:naoty_k:20120808095402j:plainf:id:naoty_k:20120808100021j:plain

.vimrcで以下のように設定しました。

" .vimrc

Bundle 'kien/ctrlp.vim'

let g:ctrlp_cmd = 'CtrlPMixed'
let g:working_path_mode = 'rc'
let g:custom_ignore = {
  ¥ 'dir': '¥.git¥|vendor/bundle¥|tmp',
  ¥ 'file': '¥.jpg$¥|¥.jpeg$¥|¥.png$¥|¥.gif$¥|¥.log'
  ¥ }
  • Ctrl-pで起動するモードを file + mru + bufferを同時に検索するMixedにしています。これで「現在のディレクトリ以下」「よく使うファイル」「バッファ」の中から検索します。
  • 'rc'モードにすることで、.gitがあるディレクトリを優先するみたいです。
  • vendor/bundleやtmpといったディレクトリや*.logのような大きいファイルを無視することで、起動をスムーズにしています。

その他いろいろ設定があるようなので、ヘルプや公式ページをご覧ください。

さくらVPS作業メモ(rubyインストールまで)

環境

  • さくらVPS 512
  • CentOS
  • naoty@local:ローカルの作業用ユーザー
  • root@sakura:さくらVPSのroot
  • naoty@sakura:さくらVPSの作業用ユーザー
  • 使いまわしてる設定ファイル:https://github.com/naoty/dotfiles ブランチはserver

sshでrootにログイン

naoty@local% ssh-keygen -R xxx.xxx.xxx.xxx
naoty@local% ssh root@xxx.xxx.xxx.xxx

作業用ユーザーの作成

root@sakura% useradd naoty
root@sakura% passwd naoty

su, sudoをwheelのみに限定

root@sakura% usermod -G wheel naoty
root@sakura% visudo
root@sakura% vi /etc/login.defs
root@sakura% exit

公開鍵でのログインに変更

naoty@local% scp .ssh/id_rsa.pub naoty@xxx.xxx.xxx.xxx:~
naoty@local% ssh naoty@xxx.xxx.xxx.xxx
naoty@sakura% mkdir .ssh
naoty@sakura% chmod 700 .ssh
naoty@sakura% mv id_rsa.pub .ssh/authorized_keys
naoty@sakura% chmod 600 .ssh/authorized_keys
naoty@sakura% sudo vi /etc/ssh/sshd_config
naoty@sakura% sudo /etc/init.d/sshd restart
naoty@sakura% exit
naoty@local% ssh sakura

yumでgit, zsh, vimをインストール

naoty@sakura% sudo yum -y update
naoty@sakura% sudo rpm -ivh http://repo.webtatic.com/yum/centos/5/latest.rpm
naoty@sakura% sudo yum -y --enablerepo=webtatic install git zsh vim-enhanced

使い回してる設定ファイルを適用

naoty@sakura% ssh-keygen -t rsa
naoty@sakura% cat .ssh/id_rsa.pub
# githubにsakuraの公開鍵を設定
naoty@sakura% git clone git@github.com:naoty/dotfiles.git
naoty@sakura% cd dotfiles
naoty@sakura% git checkout server
naoty@sakura% cd
naoty@sakura% ln -s dotfiles/.gitconfig ~/.gitconfig
naoty@sakura% ln -s dotfiles/.gitignore_global ~/.gitignore_global
naoty@sakura% ln -s dotfiles/.vimrc ~/.vimrc
naoty@sakura% mkdir -p .vim/colors
naoty@sakura% exit
naoty@local% scp -P sshd .vim/colors/railscasts.vim naoty@xxx.xxx.xxx.xxx:.vim/colors/

パスを通す

naoty@local% ssh sakura
naoty@sakura% vi .bash_profile
naoty@sakura% source .bash_profile

iptablesの設定(とりあえずsshと内部からのコネクションのみ)

naoty@sakura% sudo iptables -A INPUT -p tcp --dport sshd -j ACCEPT
naoty@sakura% sudo iptables -A INPUT -m state --state ESTABLISHED -j ACCEPT
naoty@sakura% sudo iptables -P INPUT DROP
naoty@sakura% sudo /etc/init.d/iptables save
naoty@sakura% sudo /etc/init.d/iptables restart

rvmのインストール

naoty@sakura% cp /etc/pki/tls/certs/ca-bundle.crt .
naoty@sakura% sudo curl http://curl.haxx.se/ca/cacert.pem -o /etc/pki/tls/certs/ca-bundle.crt
naoty@sakura% sudo bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer )
naoty@sakura% su -
root@sakura% usermod -G wheel,rvm naoty
root@sakura% exit
naoty@sakura% exit
naoty@local% ssh sakura

rubyのインストール

naoty@sakura% sudo yum install -y gcc-c++ patch readline readline-devel zlib zlib-devel libyaml-devel libffi-devel openssl-devel make bzip2 autoconf automake libtool bison
naoty@sakura% rvm install 1.9.3
naoty@sakura% rvm use 1.9.3 --default

rvm installでyamlがmakeできない件

naoty$ sudo bash < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer )
naoty$ su -
root# usermod -G wheel,rvm naoty
root# exit
naoty$ exit
local$ ssh
naoty$ sudo yum install -y gcc-c++ patch readline readline-devel zlib zlib-devel libyaml-devel libffi-devel openssl-devel make bzip2 autoconf automake libtool bison
naoty$ rvm install 1.9.3
...
Compiling yaml in /usr/local/rvm/src/yaml-0.1.4.
ERROR: Error running 'make ', please read /usr/local/rvm/log/ruby-1.9.3-p0/yaml/make.log
Installing yaml to /usr/local/rvm/usr
ERROR: Error running 'make install', please read /usr/local/rvm/log/ruby-1.9.3-p0/yaml/make.install.log
...
Install of ruby-1.9.3-p0 - #complete
naoty$ less /usr/local/rvm/log/ruby-1.9.3-p0/yaml/make.log
[2011-11-05 18:32:56] make 
make  all-recursive
make[1]: ディレクトリ `/usr/local/rvm/src/yaml-0.1.4' に入ります
Making all in include
make[2]: ディレクトリ `/usr/local/rvm/src/yaml-0.1.4/include' に入ります
make[2]: `all' に対して行うべき事はありません.
make[2]: ディレクトリ `/usr/local/rvm/src/yaml-0.1.4/include' から出ます
Making all in src
make[2]: ディレクトリ `/usr/local/rvm/src/yaml-0.1.4/src' に入ります
if /bin/sh ../libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H -I. -I. -I..  -I../include   -g -O2 -MT api.lo -MD -MP -MF ".deps/api.Tpo" -c -o api.lo api.c; \
        then mv -f ".deps/api.Tpo" ".deps/api.Plo"; else rm -f ".deps/api.Tpo"; exit 1; fi
../libtool: line 466: CDPATH: command not found
../libtool: line 1144: func_opt_split: command not found
libtool: Version mismatch error.  This is libtool 2.2.6b Debian-2.2.6b-2ubuntu1, but the
libtool: definition of this LT_INIT comes from an older release.
libtool: You should recreate aclocal.m4 with macros from libtool 2.2.6b Debian-2.2.6b-2ubuntu1
libtool: and run autoconf again.
make[2]: *** [api.lo] エラー 1
make[2]: ディレクトリ `/usr/local/rvm/src/yaml-0.1.4/src' から出ます
make[1]: *** [all-recursive] エラー 1
make[1]: ディレクトリ `/usr/local/rvm/src/yaml-0.1.4' から出ます
make: *** [all] エラー 2

なにこれ?(´・ω・`)

factory_girlを使ってスマートにseedデータを作成する

環境

  • rails 3.1.1
  • factory_girl 2.2.0

 requireでfactory_girlとfactoryファイルすべてを読み込むと、seeds.rbでもfactory_girlが使えます。seeds.rbでfactory_girlを使うのは、テストと同じデータをブラウザでも確認できる、seeds.rbを非常に簡潔に書くことができる、といった利点があります。

以下の例では、このようなデータを作成する例です。

db/seeds.rb

require 'factory_girl'
Dir[Rails.root.join('spec/support/factories/*.rb')].each {|f| require f }

User.delete_all
Article.delete_all

FactoryGirl.create(:naoty)
FactoryGirl.create_list(:user, 5)
FactoryGirl.create_list(:article, 100)

spec/support/factories/users.rb

FactoryGirl.define do
  factory :user do
    sequence(:name) {|n| "user #{n}" }
  end

  factory :naoty do
    name 'naoty'
  end
end

spec/support/factories/articles.rb

FactoryGirl.define do
  factory :article do
    sequence(:title) {|n| "sample title #{n}" }
    user_id { User.all.to_a.map(&:id).sample }
  end
end
naoty$ rake db:seed

さくらVPSでRailsを動かすまでのTodo

苦労の末、とうとうサーバーでRailsが動いたので、記念メモ。
どんどん加筆していく予定です。

  • プラン:さくらのVPS 512(いちばん安いやつ。月980円)
  • OS:CentOS 5.5 x86_64
  • ruby 1.9.2
  • Rails 3.1.0
  • webサーバー:nginx
  • appサーバー:unicorn
  • DB:MySQL
  • repo:bitbucket
  • rootユーザーのパスワードを設定する
  • 作業用ユーザーを作成する
  • 作業用ユーザーのパスワードを設定する
  • su, sudoの権限を制限する
  • $PATHを追加する
  • パスワード認証から公開鍵認証に変更する
  • rootでのログインを禁止する
  • sshのポートを22番から変更する
  • iptablesによるパケットフィルタリングを設定する
  • yumをアップデートする
  • gitをインストールする
  • rvmをインストールする
  • ruby1.9.2をインストールする
  • mysqlをrpmでインストールする
  • nginxをビルド
  • nginxの自動起動を設定する
  • unicorn、bundlerをgemにインストール
  • bitbucketからプロジェクトをgit clone
  • bundle install --path vendor/bundle
  • 本番用DB作成
  • マイグレーション
  • unicornの設定ファイル(config/unicorn.rb)を作成して、unicornを起動
  • unicornに合わせてnginxの設定ファイル(nginx.conf)を修正して、リロード
  • 静的ファイルのプリコンパイルの設定を変更

参考図書

ハイパフォーマンスHTTPサーバ Nginx入門

ハイパフォーマンスHTTPサーバ Nginx入門


エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド

エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド

nginx+rvmでPassengerが動いてない?

という構成でRailsアプリケーションを動かそうとしたのですが、どうやらpassengerが動いてないみたいで(ずっと403 Forbidden)いろいろ調べてみました。

$ ps aux | grep Passenger
root      6819  0.0  0.3  16908  1740 ?        Ssl  Oct23   0:00 PassengerWatchdog
root      6822  0.0  0.4  32704  2404 ?        Sl   Oct23   0:00 PassengerHelperAgent
root      6824  0.0  1.6 107472  8172 ?        Sl   Oct23   0:00 Passenger spawn server

nobody    6845  0.0  0.5  51544  2904 ?        Sl   Oct23   0:00 PassengerLoggingAgent
naoty     7639  0.0  0.1   6040   568 pts/0    R+   12:55   0:00 grep Passenger
  • Passengerのプロセスは4つ起動しているようです。
$ rvmsudo passenger-status
[sudo] password for naoty:
----------- General information -----------
max      = 6
count    = 0
active   = 0
inactive = 0
Waiting on global queue: 0

----------- Application groups ----------
  • ん?count=0ということはひとつもプロセスが起動していないようです…

やっぱりpassengerが動いてないからうまくいかないんだろうか…?(´・ω・`)