Satoryu's Diary

Rubyが好きなプログラマーの日記。日々の生活、開発に関するメモとか考えとか。


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 の内部は、

  1. let または let!
  2. beforeafter または around
  3. もしあれば、 subject
  4. itまたはspecify
  5. describe または context

の順で記述する。

  • 各項目について連続して記述する場合、それぞれの記述の間に空行は入れず、
  • 異なる項目間に空行を入れる。
  • 項目2については、
    • beforeafteraround の順に、
    • それぞれ、: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を読むと、参考になることがたいてい書いてあるのですが、構造については見たことがないのでまとめてみました。

あれやこれやという議論のネタにしていただければ幸いです。

参考

Tags: Ruby RSpec TDD

最近の投稿

翻訳しました(ちょっとだけ)

follow us in feedly