「テストコードの期待値はDRYを捨ててベタ書きする」のテストコードをMinitestで書く・RSpecらしく書く


## はじめに

これは昨日公開した「[テストコードの期待値はDRYを捨ててベタ書きする ～テストコードの重要な役割とは？～ - Qiita](http://qiita.com/jnchito/items/eb3cfa9f7db752dcb796)」という記事のおまけ記事です。

元の記事はRSpecで書いたので「RSpecわからん」と思われた人が中にはいたようです。
そこで、RSpecではなくMinitestでテストコードを書いてみました。

また、「RSpecには共通化の機能があるんだからそれを使うべきだ」「itの中のアサーションは1つにすべきだ」という意見もいただいたので、より「RSpecらしい」書き方を考えてみました。

この記事ではこれら2パターンのテストコードを紹介します。

## Minitestで書いたテストコード

元記事では以下のようなRSpecのコードを紹介しました。

```ruby:cloth_spec.rb
describe Cloth do
  describe '#half_price' do
    it '半額の値段を計算する' do
      cloth = Cloth.new('RSpec Tシャツ', 1000)
      expect(cloth.half_price).to eq 500
      
      cloth = Cloth.new('RSpec Tシャツ', 2000)
      expect(cloth.half_price).to eq 1000
      
      cloth = Cloth.new('RSpec Tシャツ', 999)
      expect(cloth.half_price).to eq 499
    end
  end
end
```

これと同等のテストコードをMinitestで書くと以下のようになります。

```ruby:cloth_test.rb
class ClothTest < Minitest::Test
  def test_half_price
    cloth = Cloth.new('RSpec Tシャツ', 1000)
    assert_equal 500, cloth.half_price

    cloth = Cloth.new('RSpec Tシャツ', 2000)
    assert_equal 1000, cloth.half_price

    cloth = Cloth.new('RSpec Tシャツ', 999)
    assert_equal 499, cloth.half_price
  end
end
```

### 親クラスとメソッド名のルール

Minitestで書く場合は、`Minitest::Test`を継承したテストクラスを作ります。
さらに、`test_`で始まるpublicメソッドを作ります。
これがテスト実行時にMinitestから呼ばれるメソッドになります。

### assert_equalの使い方

AがBに等しいことを検証する場合は `assert_equal B, A` のように書きます。
期待値(expected)が第1引数に、実際の値(actual)が第2引数となる点に注意してください。

### person_spec.rbをMinitestで書き直す

同じ要領でもう一つのテストコードも変換してみましょう。

```ruby:person_spec.rb
describe Person do
  describe '#format_date_of_birth' do
    it '生年月日を和暦で表示すること' do
      person = Person.new('Taro', Date.parse('1977-06-06'))
      expect(person.format_date_of_birth).to eq '昭和52年6月6日'

      person = Person.new('Taro', Date.parse('1988-06-06'))
      expect(person.format_date_of_birth).to eq '昭和63年6月6日'

      person = Person.new('Taro', Date.parse('1989-06-06'))
      expect(person.format_date_of_birth).to eq '平成1年6月6日'

      person = Person.new('Taro', Date.parse('2016-06-06'))
      expect(person.format_date_of_birth).to eq '平成28年6月6日'
    end
  end
end
```

これはMinitestで書くとこうなります。

```ruby:person_test.rb
class PersonTest < Minitest::Test
  def test_format_date_of_birth
    person = Person.new('Taro', Date.parse('1977-06-06'))
    assert_equal '昭和52年6月6日', person.format_date_of_birth

    person = Person.new('Taro', Date.parse('1988-06-06'))
    assert_equal '昭和63年6月6日', person.format_date_of_birth

    person = Person.new('Taro', Date.parse('1989-06-06'))
    assert_equal '平成1年6月6日', person.format_date_of_birth

    person = Person.new('Taro', Date.parse('2016-06-06'))
    assert_equal '平成28年6月6日', person.format_date_of_birth
  end
end
```

コードの読み方は`ClothTest`の場合と同じなので省略します。

## RSpecらしく書いたテストコード

RSpecにはdescribe/contextブロックをネストさせたり、letやsubjectといった機能を使ったりして、テストコードの重複を無くすことができます。
詳しい話はコードを見ながら説明した方がわかりやすいと思います。

これは元のテストコードです。

```ruby:cloth_spec.rb
describe Cloth do
  describe '#half_price' do
    it '半額の値段を計算する' do
      cloth = Cloth.new('RSpec Tシャツ', 1000)
      expect(cloth.half_price).to eq 500
      
      cloth = Cloth.new('RSpec Tシャツ', 2000)
      expect(cloth.half_price).to eq 1000
      
      cloth = Cloth.new('RSpec Tシャツ', 999)
      expect(cloth.half_price).to eq 499
    end
  end
end
```

これをRSpecらしく書くとこうなります。

```ruby:cloth_spec.rb
describe Cloth do
  describe '#price_with_tax' do
    let(:cloth) { Cloth.new('RSpec Tシャツ', price) }
    subject { cloth.half_price }
    context '割り切れる場合' do
      let(:price) { 1000 }
      it { is_expected.to eq 500 }
    end
    context '割り切れる場合・その2' do
      let(:price) { 2000 }
      it { is_expected.to eq 1000 }
    end
    context '端数が出る場合' do
      let(:price) { 999 }
      it { is_expected.to eq 499 }
    end
  end
end
```

### describeとcontextについて

describeとcontextはそれぞれテストをグループ化するために使用します。
どちらも役割は全く同じ（エイリアスの関係）ですが、contextは「文脈」や「背景」といった意味なので、特にテスト条件を表す場合によく使われます。

### subjectについて

subjectはテスト対象となる「実際の値」を明示的に示すために使います。
たとえば、subjectを使って書いた以下のコードは、

```ruby
subject { cloth.half_price }
it { is_expected.to eq 500 }
```

以下のコードと同じ意味になります。

```ruby
it 'xxx' do
  actual = cloth.half_price
  expect(actual).to eq 500
end
```

### letについて

letは遅延評価されるローカル変数のようなものです。
たとえば、以下のように`let(:price)`を宣言すると、

```ruby
let(:cloth) { Cloth.new('RSpec Tシャツ', price) }
context '割り切れる場合' do
  let(:price) { 1000 }
  # ...
end
```

次のようなコードを書いたことと同じ意味になります。

```ruby
let(:cloth) { Cloth.new('RSpec Tシャツ', 1000) }
context '割り切れる場合' do
  # ...
end
```

また、`let`はdescribe/contextブロックごとに異なる値を宣言できます。
なので、別のブロックの`let(:price)`で別の値を宣言した場合は、

```ruby
let(:cloth) { Cloth.new('RSpec Tシャツ', price) }
# 他のcontextブロック...
context '端数が出る場合' do
  let(:price) { 999 }
  # ...
end
```

「割り切れる場合」と異なる値（この場合は999）がセットされることになります。

```ruby
let(:cloth) { Cloth.new('RSpec Tシャツ', 999) }
# 他のcontextブロック...
context '端数が出る場合' do
  # ...
end
```

### itの長さについて

元のテストコードでは次のように、`it`の中に複数の`expect( ).to eq`を書いていました。

```ruby
it '半額の値段を計算する' do
  cloth = Cloth.new('RSpec Tシャツ', 1000)
  expect(cloth.half_price).to eq 500
  
  cloth = Cloth.new('RSpec Tシャツ', 2000)
  expect(cloth.half_price).to eq 1000
  
  cloth = Cloth.new('RSpec Tシャツ', 999)
  expect(cloth.half_price).to eq 499
end
```

しかし、これだと上から順番に実行されるため、途中でテストが失敗するとその先のテストは実行されません。
なので、3件中何件がパスして何件がパスしないのか実行結果から読み取ることができない、というデメリットがあります。

RSpecらしく書いたテストコードではcontextでテストをそれぞれグループ化しました。

```ruby
context '割り切れる場合' do
  let(:price) { 1000 }
  it { is_expected.to eq 500 }
end
context '割り切れる場合・その2' do
  let(:price) { 2000 }
  it { is_expected.to eq 1000 }
end
context '端数が出る場合' do
  let(:price) { 999 }
  it { is_expected.to eq 499 }
end
```

こうするとそれぞれの`it`は独立して実行されるので、3件中何件がパスして何件がパスしないのか確実に把握できます。

### もっと詳しく

上で説明したRSpecの機能については以下の記事でさらに詳しく説明しています。
ピンと来なかった人はこちらを読んでみてください。

[使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」 - Qiita](http://qiita.com/jnchito/items/42193d066bd61c740612)

### person_spec.rbをRSpecらしく書く

もう一つのテストコード、`person_spec.rb`もRSpecらしく書き直してみましょう。
変更前はこんなテストコードでした。

```ruby:person_spec.rb
describe Person do
  describe '#format_date_of_birth' do
    it '生年月日を和暦で表示すること' do
      person = Person.new('Taro', Date.parse('1977-06-06'))
      expect(person.format_date_of_birth).to eq '昭和52年6月6日'

      person = Person.new('Taro', Date.parse('1988-06-06'))
      expect(person.format_date_of_birth).to eq '昭和63年6月6日'

      person = Person.new('Taro', Date.parse('1989-06-06'))
      expect(person.format_date_of_birth).to eq '平成1年6月6日'

      person = Person.new('Taro', Date.parse('2016-06-06'))
      expect(person.format_date_of_birth).to eq '平成28年6月6日'
    end
  end
end
```

これをRSpecらしく書き直すと次のようになります。

```ruby:person_spec.rb
describe Person do
  describe '#format_date_of_birth' do
    let(:person) { Person.new('Taro', Date.parse(date)) }
    subject { person.format_date_of_birth }
    context '昭和生まれの場合' do
      let(:date) { '1977-06-06' }
      it { is_expected.to eq '昭和52年6月6日' }
    end
    context '昭和最終年生まれの場合' do
      let(:date) { '1988-06-06' }
      it { is_expected.to eq '昭和63年6月6日' }
    end
    context '平成元年生まれの場合' do
      let(:date) { '1989-06-06' }
      it { is_expected.to eq '平成1年6月6日' }
    end
    context '平成生まれの場合' do
      let(:date) { '2016-06-06' }
      it { is_expected.to eq '平成28年6月6日' }
    end
  end
end
```

ポイントは`cloth_spec.rb`の場合と同じなので、説明は省略します。

## まとめ

というわけで、この記事では「テストコードの期待値はDRYを捨ててベタ書きする」のテストコードをMinitestで書く場合と、RSpecらしく書く場合のサンプルコードを紹介しました。
みなさんがMinitestやRSpecを使ってテストコードを書く際の参考になれば幸いです。

## あわせて読みたい

### RSpecとMinitest、使うならどっち？

僕が[関西Ruby会議06](http://regional.rubykaigi.org/kansai06/)で発表した、MinitestとRSpecの違いを比較したスライドです。
「Minitestしかようわからん」「RSpecしかようわからん」という人が見ると参考になるかもしれません。

[RSpecとMinitest、使うならどっち？ / #kanrk06 // Speaker Deck![Kobito.QWjaif.png](https://qiita-image-store.s3.amazonaws.com/0/7465/249468d6-4472-217c-a38a-c435ee08f3f1.png "Kobito.QWjaif.png")
](https://speakerdeck.com/jnchito/number-kanrk06)



### RSpecユーザのためのMinitestチュートリアル

「[Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp/)」の追加コンテンツとして提供している電子書籍です。
RSpecを使い慣れている人が「Minitestってどんなフレームワークなんだろう？」と思ったときに読むとちょうどいい内容になっています。

[RSpecユーザのためのMinitestチュートリアル![Kobito.TYeYp5.png](https://qiita-image-store.s3.amazonaws.com/0/7465/b6de0753-7642-1ae9-0064-bc153d279348.png "Kobito.TYeYp5.png")
](https://leanpub.com/minitest-tutorial-for-rspec-users)