SWIFTという観点で良いテストが書けているか評価する

Rails 4 Test Prescriptionsをポチったので読んでいます。その中の”What makes great tests”という章の中で、良いテストかどうか評価する観点が書かれていたのでメモ。

SWIFTという観点

  • Straightforward(率直であること)
  • Well-defined(明確に定義されていること)
  • Independent(独立していること)
  • Fast(実行が速いこと)
  • Truthful(テストの内容が正しいこと)

Straightforward(率直であること)

テストを見たときに即座にテストの目的が分かるようになっていること、というのが最初の観点です。つまり率直であること。

例えばこんなテストがあったとき、

before do 
  User.create!(points: 32.1)
  User.create!(points: 5.3)
end

# ...

describe '.all_total_points' do
  it 'should add to 37' do 
    expect(User.all_total_points).to eq(37)
  end
end
  • 「37」という数字が何を意味しているのか分からない
  • all_total_pointsのどんな振る舞いをテストしたいのか分からない

という問題があります。確かに書いている人にとっては何らかのテストにはなっているのでしょうが、これだけだと分かりませんよねー。

describe '.all_total_points' do
  before do 
    User.create!(points: 32.1)
    User.create!(points: 5.3)
  end

  it 'rounds total points to the nearest integer' do 
    expect(User.all_total_points).to eq(37)
  end
end

「合計ポイントから最も近い整数を返す」と書いてあれば分かりやすいです。そのためのテストデータも近くに書いてあると、より良いですね。

Well-defined(明確に定義されていること)

何回テストを実行しても同じ結果になること、というのが2つ目の観点です。テスト対象の値がコロコロ変わらずに明確に決められていること、つまり明確に定義されていること。

例えばこんなコードがあるとします、

class RandomStream
  def next
    rand()
  end
end

実行する度に異なる値が返るので、このクラスを利用しているテストによっては実行する度に成功したり失敗したりするかも知れません。これだとテストにならないので、RandomStreamクラスをモックにするなりして、必ず再現性のある状態にしてテストするのが良いです。

Independent(独立していること)

テスト同士が依存していたり、外部データに依存しているようなテストでないこと、というのが3つ目の観点です。すなわち独立していること。

例えば実際にAPIで外部データを取得しに行ってテストしているような場合、その外部データは変わるかも知れないのでそもそもwell-definedではないし、データによっては成功するかも知れないし失敗するかも知れないのでテストになっていないです。

あるテストでこういう風に値が変わっているから、それを利用してこのテストではこういう値になることをテストする、というのもよろしくないです。なぜなら前提になっているテストの振る舞いが変わると、後続のテストも落ちてしまうから。ちょっとした振る舞いを変えるために様々なテストを変更しなくてはいけないので、テストの保守性が下がってしまいますね。

Fast(実行が速いこと)

テスト駆動で開発していると、テストの実行の遅さが開発のネックになってしまうのでテストの実行スピードは重要だよね、というのが4つ目の観点です。

Railsならspringやzeusなどのプリローダを使ってテストの初速を上げたり、余計な機能をオフにしたり(例えば画像アップロード時に自動的にサムネイルを作成する機能をテストの場合はオフにする等)、API接続の部分はモックにしたりといったことが有効かなと思います。

テストが遅いと、テスト書きたくなくなるので、重要ですよねー。

Truthful(テストの内容が正しいこと)

テスト項目とテストの内容が合っていること、というのは当たり前だけど重要な最後の観点です。

it 'adds 10 points' do 
  expect(10.add_ten_points).to eq 21
end

なんてテストがあると辛いだけですよね。

it 'shows the project section' do
  get :dashboard
  expect(response).to have_selecter('h2', text: 'My Projects')
end

なんていうテストも、h2で囲まれたMy Projectsという文言が他のページにもあるような場合に意味をなさないし、文言が変わるだけで落ちるのでつらいですよね。

it 'shows the project section' do
  get :dashboard
  expect(response).to have_selecter('h2#projects')
end

せめてDOMなら、そういったズレもなくなるのでは。しかし例とはいえ、このテスト自体どうかと思う。

以上、SWIFTという5つの観点をお伝えしました。テストコードに対するテストコードはないので、テストコードこそしっかりコードレビューすべきかなと思います。そういうわけで、そんなコードレビューの際の観点として使えるのではないか、と思いました。