ぱろっと・すたじお

技術メモなどをまったりと / my site : http://parrot-studio.com/

続・ある技術者がRSpecに目覚めるまで

ある意味で以前の続きです


ある技術者がRSpecに目覚めるまで - ぱろっと・すたじお


あれから、仕事でもRDGCの開発でも、
全てのコードにRSpecを書くようになりました
同時に、コード管理もSubversionからGitへ移行しています


前回も書いたように、完全な「テストファースト」ではないのですが、
もはやRSpecなしでは生きられない体に(lll゚Д゚)


ちなみに、今月の「Web+DB Press」にもTDD/BDDの話が出てます

WEB+DB PRESS Vol.56

WEB+DB PRESS Vol.56


今回は前回書いた内容に加えて、個人的に気づいたことをいくつか

必ずしも「テストファースト」にこだわらない


前回も書いたことですが、テストファーストというのは正直難しいです


Railsのようなフレームワークが存在し、
その上に載せるコードであれば、ある程度実装が自明ですので、
テストを先に書くことは可能です


しかし、RDGCとか仕事で書いているデータ処理システムのように、
自分がフレームワークを作る側に立っていると、
最初から妥当な設計はまず見えません


擬似的なコードやコメントを書き連ね、
「なんとなく動きそうなコード」までとりあえず書いたところで、
その仕様をRSpecで固める・・・という感じでやってます


このとき、RSpecを書く前のコードは適当に書いたコードなので、
結局のところspecを満たせず、「Red」が返ります*1
変則的ではありますが、一応TDD/BDDっぽい流れになってるんじゃないかと

Gitは細かい単位でcommit


以前紹介したGitの本にもありますが、
一つのcommitはできるだけ小さくすべきです


そうすることで、いらないcommitだけ排除したり、
commit内容を訂正したり、順番を変えることも可能になります


テストの側面から見ると、ある程度骨格が見えたところで・・・

  • 1つだけクラスをcommit
  • そのクラスに対するRSpecを記述
  • specを流して修正していく
  • specを全部満たしたら再commit & specをcommit
  • 次のクラスへ

・・・・というように、一つずつ処理するようにしています


逆にこういった個別commit/spec定義ができない場合、
後述の「設計がおかしい」に該当する場合がほとんどです

RSpecが書けない/書きづらい=設計がおかしい(断言)


フレームワークである以上、複数のオブジェクトが協調動作するのは当然ですが、
一番低レイヤーのオブジェクトからspecを重ねていけば、
どんなに複雑なオブジェクトの動作も保証できるはずです


rdgc-dmで言えば、Boardクラスが最も上位レイヤーのオブジェクトですが、
Tile => Area => Road/Room => Boardと、下から順にspecが連なっており、
各クラスに対するspecはわりとシンプルになってます


しかし、Ver0.1のコードはシンプルな構造になっておらず、
お互いに依存関係があったため、specを定義しづらい構造になっていました


ここからさっきの順でspecを書いていくことで、
各クラスの立ち位置がはっきりし、
綺麗にリファクタリングできたのです(`・ω・´) b


メソッドのexampleについても同じことが言えます


あるメソッドを「説明するコード」がうまく書けないとか、
書くのが異常に面倒だという場合、
確実にそのメソッドの粒度がおかしいか、目的がはっきりしてないはずです


RSpecの各exampleは小さくまとめた方が書きやすいはず・・・というより、
小さくまとめないと"it"の後に説明文が書けないので、
必然的に各メソッドも小さく分割されていきます


また、specを書くこと=他者の視点でオブジェクトを利用しようとすることなので、
何かメソッドを追加するとspecがシンプルまとまる・・・なんて場合、
迷わずそのメソッドを追加すべきです


今は必要性が見えなくても、
きっと誰か・・・他の誰かや1ヶ月後の自分・・・の役に立ちます

Rakeと連動させるととてもうれしい


そうやって積み重ねたspecですが、数が増えてくると、
正直実行するのが面倒になってきます(´・ω・`)


同じディレクトリにあるうちは「spec -c -fs *_spec.rb」のように実行できますが、
ディレクトリをまたぐと、とたんにテストしづらくなります


そこで、Rakeをうまく使うことで、
コマンド一発で好きな粒度のテストを流せるようになり、
飛躍的にテスト効率が上がり、テストが楽しくなります


以下はrdgc-dmのspec実行で実際に使っている定義の抜粋です

require 'rake'
require 'spec/rake/spectask'

RDGC_TARGET = [:util, :map, :maker] # これを増やすだけ

namespace :rdgc do

  RDGC_TARGET.each do |target| # 個別テスト
    Spec::Rake::SpecTask.new(target) do |spec|
      spec.libs << 'lib' << 'spec'
      spec.spec_files = FileList["spec/rdgc/#{target}/*_spec.rb"]
      spec.spec_opts = ["-c -fs"]
    end

    task target
  end

  Spec::Rake::SpecTask.new(:all) do |spec| # 全体テスト
    spec.libs << 'lib' << 'spec'
    spec.spec_files = FileList['spec/rdgc/**/*_spec.rb']
    spec.spec_opts = ["-c"]
  end

  task :all
end

このへんの柔軟な記述力が実にRubyっぽいですね


後は「rake rdgc:util」とか「rake rdgc:all」とか叩けば、
定義した粒度で全部テストを実行してくれます



各所でも言われているとおり、TDD/BDDは不安を取り除くためのものです


「テストで固めてある」という安心感が、大胆なリファクタリングを可能とし、
他者に自信を持って公開できる根拠にもなります


昔と違い、テストの記述はかなり簡単になってますので、
過去に挫折した(私のような)方も、ぜひ一度試してみては(`・ω・´) b

*1:この時点で妥当すぎると「Green」になることもあるので「変則的」なのです