続・ある技術者がRSpecに目覚めるまで
ある意味で以前の続きです
ある技術者がRSpecに目覚めるまで - ぱろっと・すたじお
あれから、仕事でもRDGCの開発でも、
全てのコードにRSpecを書くようになりました
同時に、コード管理もSubversionからGitへ移行しています
前回も書いたように、完全な「テストファースト」ではないのですが、
もはやRSpecなしでは生きられない体に(lll゚Д゚)
ちなみに、今月の「Web+DB Press」にもTDD/BDDの話が出てます
- 作者: 赤松祐希,紀平拓男,牧大輔,西林孝,中島聡,中島拓,角田直行,はまちや2,舘野祐一,きしだなおき,和田裕介,伊藤直也,大沢和宏,塙与志夫,増井俊之,ミック,WEB+DB PRESS編集部
- 出版社/メーカー: 技術評論社
- 発売日: 2010/04/24
- メディア: 大型本
- 購入: 14人 クリック: 180回
- この商品を含むブログ (40件) を見る
今回は前回書いた内容に加えて、個人的に気づいたことをいくつか
必ずしも「テストファースト」にこだわらない
前回も書いたことですが、テストファーストというのは正直難しいです
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」になることもあるので「変則的」なのです