Rubyが好きなプログラマーの日記。日々の生活、開発に関するメモとか考えとか。
自分が、真面目にテストコードを書きはじめて、だいたい4年くらい経ちました。
その間、いろんなgemとか、アプリケーションのソースを読んできたけど、
見やすい構造があるなぁ
と思ったので、自分なりに見やすいと思えるものを、ここに書いてみる。
実装本体だけでなく、テストコード自体の読みやすさも大切だと思う。
ディレクトリの階層、命名は、実装に合わせる。例えば、プロジェクトのルートディレクトリ直下で、
project/
  bin/
  lib/
    my_app/
      implementation.rb
というソースコードがあった場合、そのテストは、
project/
  spec/
    my_app/
      implementation_spec.rb
とする。
これは、
テストは仕様書である。
という考えに基づいて、実装とそれに対応する仕様が書かれている場所を類推しやすくするためである。
specファイルで取り扱うべきクラス、またはモジュールは1つのみ実装においても、もともと1つのファイルに含まれている対象となるクラスはなるべく少ない方が分かりやすい。
行数が少なく、他に比べて関連性の高い複数のクラスまたはモジュールが1つのファイル内に納められることもあるが、この場合は、1つ前の規約を優先し、ファイル内。
describeで対象となるクラスまたはモジュールを指定する。例えば、my_app/implementation.rb に、
class MyApp::Implementation
end
spec/my_app/implementation_spec.rb 内では、
describe MyApp::Implementation do
  # examples 
end
のように書く。上述の通り、複数のクラスまたはモジュールが1つのファイルに実装されている場合、
describe MyApp::Implementation do
  # examples 
end
describe MyApp::Implementation::InnerModule do
  # examples 
end
のように実装する。
describeの内側には対象となるメソッドを書く。以下のような実装にあるそれぞれのメソッドについて単体テストを書くとき、
class MyApp::Implementation
  def self.bar
  end
  def foo(arg1, arg2=nil)
  end
end
対象のメソッドごとにdescribe で宣言する。
describe MyApp::Implementation do
  describe '.bar' do
  end
  describe '#foo' do
  end
end
複数の条件をごとにテストする際には、contextを使いそれぞれの条件について宣言し、before を用いて前提条件を表現するコードを記述する。
describe MyApp::Implementation do
  describe '#foo' do
    let(:myapp) { MyApp::Implementation.new }
    context 'When given 1 arg' do
       before do
         myapp.foo('str')
       end
    end
    context 'When given 2 args' do
       before do
         myapp.foo('str', 1234)
       end
    end
  end
end
describe ないし context の内部は、
let または let!before、after または aroundsubjectitまたはspecifydescribe または contextの順で記述する。
before、after、around の順に、:all、そして:each の順で記述する。subject を用いる場合はitを、それ以外ではspecifyを使う。describe MyApp::Implementation do
  describe '#foo' do
    let(:args1) { [:aaa] }
    let(:args2) { [:aaa, 1234] }
    subject { MyApp::Implementation.new }
    its(:foo) { should be_present }
    context 'Given 1 argument' do
    end
    context 'Given 2 arguments' do
    end  
end
悩んだらBetter Specsを読むと、参考になることがたいてい書いてあるのですが、構造については見たことがないのでまとめてみました。
あれやこれやという議論のネタにしていただければ幸いです。