2014年04月24日 [長年日記]
_ 俺が考えたさいきょうのRSpecコーディング規約 - 構造編
自分が、真面目にテストコードを書きはじめて、だいたい4年くらい経ちました。
その間、いろんなgemとか、アプリケーションのソースを読んできたけど、
見やすい構造があるなぁ
と思ったので、自分なりに見やすいと思えるものを、ここに書いてみる。
実装本体だけでなく、テストコード自体の読みやすさも大切だと思う。
前提
- テストコードはテストするためだけでなく、実装を理解する助けになるものであるべき。
- ここに書いてあることは決定事項ではないので、改善されるべきことがらである。
- 規約はみんなのために
なぜ読みやすいテストを心がけるべきか
- レビューし易いから
- 仕様を理解しやすいから
- 読みづらいと、仕様が複雑なのかテストの実装が複雑なのかがわからないから
- メンテナンスしやすいから
規約
ディレクトリ構造
ディレクトリの階層、命名は、実装に合わせる。例えば、プロジェクトのルートディレクトリ直下で、
project/
bin/
lib/
my_app/
implementation.rb
というソースコードがあった場合、そのテストは、
project/
spec/
my_app/
implementation_spec.rb
とする。
これは、
テストは仕様書である。
という考えに基づいて、実装とそれに対応する仕様が書かれている場所を類推しやすくするためである。
テストの構造
1つの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またはaround- もしあれば、
subject itまたはspecifydescribeまたはcontext
の順で記述する。
- 各項目について連続して記述する場合、それぞれの記述の間に空行は入れず、
- 異なる項目間に空行を入れる。
- 項目2については、
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を読むと、参考になることがたいてい書いてあるのですが、構造については見たことがないのでまとめてみました。
あれやこれやという議論のネタにしていただければ幸いです。