Ruby標準のテスティングフレームワークで手軽にテストコードを書く方法


## はじめに

僕が一番使い慣れているテスティングフレームワークはRSpecです。
普段の業務でも大半はRSpecでテストコードを書いています。

しかし、ごくごく簡単なRubyのコードを書く場合は「わざわざRSpecを書くのは大げさかな」と思うことがあります。
簡単なコードであれば、テストコードも簡単なものになるので`assert_equal`が使えれば十分だったりします。

というわけで今回の記事ではgemを使わず、Ruby標準のテスティングフレームワークでテストコードを書く方法をまとめてみます。


## やりたいこと

- 「素のRuby」でぱぱっとシンプルなテストを書きたい（`assert_equal`だけで十分なケースを想定）
- gemのインストールはしたくない、他の人にもさせたくない
- Ruby 2.0以降ならどのRubyでも動いてほしい

つまり、 **「いつでもどこでも動くテストコードを書きたい」** というのが今回の目的です。

## いつでもどこでも動くテストコードの雛形

というわけで、いつでもどこでも動くテストコードの雛形を書くとこんな感じです。

```ruby
# test_sample.rb
require 'test/unit'

class Sample
  def self.greeting
    'Hello, world!'
  end
end

class TestSample < Test::Unit::TestCase
  def test_greeting
    assert_equal 'Hello, world!', Sample.greeting
  end
end
```

### コードの説明
`Test::Unit::TestCase`を継承したクラスを用意し、`test_xxx`というメソッドを定義するとそのメソッドがテストの実行対象になります。
ここではそれぞれ`TestSample`クラスと`test_greeting`メソッドがそれに該当します。

`assert_equal (期待値), (実際の値)`で実行結果を検証します。（アサーション）
両者が一致すればテストがパスし、一致しない場合はテストが失敗します。

`test_xxx`というメソッドはクラス内に複数あっても構いません。
また、1つのテストメソッド内に`assert_equal`を複数書くのもOKです。
（とはいえ、原則として1テストメソッドにつき1アサーションとするのが望ましいです）

参考までにテストメソッドやアサーションを複数書く場合のコード例を以下に示します。

```ruby
class TestSample < Test::Unit::TestCase
  def test_greeting
    assert_equal 'Hello, world!', Sample.greeting
  end
  
  def test_calc
    assert_equal 2, (1 + 1)
    assert_equal 1, (2 - 1)
    assert_equal 6, (2 * 3)
    assert_equal 3, (9 / 3)
  end
end
```

### 実行方法
実行方法は`ruby (ファイル名)`でOKです。
Ruby 2.2でもRuby2.0でも動作します。（ただし結果の表示が若干異なります）

```bash
# Ruby 2.2で動かした場合
$ ruby test_sample.rb
Loaded suite test_sample
Started
.

Finished in 0.000526 seconds.

1 tests, 1 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

1901.14 tests/s, 1901.14 assertions/s
```

```bash
# Ruby 2.0で動かした場合
$ ruby test_sample.rb 
Run options: 

# Running tests:

Finished tests in 0.002585s, 386.8472 tests/s, 386.8472 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

ruby -v: ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin14.1.0]
```

## 「あれ？Ruby標準のテストフレームワークってMinitestじゃなかったっけ？」

上で紹介したテストコードには「minitest」の文字が出てきません。

普段はRSpecをメインで使っているので、あまりRuby標準のテスティングフレームワークは詳しく見てこなかったのですが、「Ruby 1.9.1以降はMinitestが標準になった」という話をなんとなく聞いていました。

が、Ruby 2.2からはどうも話が変わっているようです。
ネットの情報を元にざっくりと経緯をまとめると、このようになります。

- 1.9.0以前 => Test::Unitが標準
- 1.9.1から2.1まで => Minitestが標準
- 2.2以降 => Minitest 5.4.3とTest::Unit 3.0.8が標準

（参考文献：[Rubyのテスティングフレームワークの歴史（2014年版）](http://www.clear-code.com/blog/2014/11/6.html)）

もうちょっと細かくいうと、こんなふうになっているようです。

- 1.9.0以前
  - Test::Unitが標準なので（当然）  **Test::Unit向けのコードが動く。**
  - Minitestは含まれないので（当然） Minitestのコードは動かない。 
- 1.9.1から2.1まで
  - MinitestがTest::Unit互換のラッパークラスを提供しているため、 **Test::Unit向けのコードも動く。**
  - もちろんMinitestのコードも動く。ただし、Minitestがサポートする構文はバージョン4相当。
- 2.2以降
  - Minitestがバージョン5になり後方互換性がなくなったため、Test::Unitが復活。 **引き続きTest::Unit向けのコードがサポートされる。**
  - Minitestのコードも引き続き動く。ただし、Minitestのバージョンが上がったため、Minitest 4のテストコードを実行すると警告が出る。

もっと厳密にいうと、こんな提供方法の違いもあります。

- 2.1以前
  - テスティングフレームワークはRuby標準ライブラリの一部として提供されていた。
- 2.2以降
  - MinitestもTest::Unitも標準ライブラリではなく tarball （gem）としてバンドルされる。

以上の内容を大雑把にまとめるなら、 **「Test::Unitのテストコードは互換性の観点から昔も今も将来もサポートされる（と思う）」** ということになります。

### 余談：Test::Unit？test-unit？

上の説明では全部まとめて「Test::Unit」と呼びましたが、厳密には「Test::Unit」と「test-unit」は微妙に指しているものが異なるそうです。

- Test::Unit => Ruby 1.9.0までのRuby本体に入っていた標準ライブラリ
- test-unit => Ruby本体から分離され、gemとして開発されているTest::Unit

詳しくは「[Rubyのテスティングフレームワークの歴史（2014年版）](http://www.clear-code.com/blog/2014/11/6.html)」をご覧ください。

## 参考：Minitest向けに書くとこうなる

ちなみに、上のテストコードの雛形をMinitest向けに書くとこうなります。

```ruby
# Minitest 4までのコード
require 'minitest/autorun'

class Sample
  def self.greeting
    'Hello, world!'
  end
end

class TestSample < MiniTest::Unit::TestCase
  def test_greeting
    assert_equal 'Hello, world!', Sample.greeting
  end
end
```

```ruby
# Minitest 5のコード
require 'minitest/autorun'

class Sample
  def self.greeting
    'Hello, world!'
  end
end

class TestSample < Minitest::Test
  def test_greeting
    assert_equal 'Hello, world!', Sample.greeting
  end
end
```

違いは継承元のクラスです。
Minitest 4では`MiniTest::Unit::TestCase`を、Minitest 5では`Minitest::Test`をそれぞれ継承しています。

### Minitestの互換性問題
Ruby 2.2でMinitest 4のコードを動かすと、"MiniTest::Unit::TestCase is now Minitest::Test."という警告が出ます。

また、Ruby 2.1以前のRubyでMinitest 5のコードを動かすと、"\`const_missing': uninitialized constant MiniTest::Test (NameError)"というエラーが出てテストコードが動きません。
（ただし、Ruby 2.1以前の環境でも`gem install minitest`で最新版のMinitestをインストールすれば、Minitest 5のコードが動くようになります。）

その他、Minitest 4とMinitest 5の互換性問題についてはMinitestのChange logを参考にしてください。
（普段使わないので僕もあまり詳しくないです）

- https://github.com/seattlerb/minitest/blob/master/History.txt => "=== 5.0.0 / 2013-05-10"

## 参考：RSpecで書くとこうなる

ついでに上の雛形をRSpecでも書いてみましょう。

```ruby
# sample_spec.rb
class Sample
  def self.greeting
    'Hello, world!'
  end
end

describe Sample do
  example 'greeting' do
    expect(Sample.greeting).to eq 'Hello, world!'
  end
end
```

実行にはgemのインストールが必要です。

```bash
gem install rspec
```

実行コマンドは`rspec （ファイル名）`です。

```bash
$ rspec sample_spec.rb 
.

Finished in 0.00115 seconds (files took 0.10203 seconds to load)
1 example, 0 failures
```

RSpecの基本については以前僕が書いたこちらの記事を参考にしてみてください。

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

## 単純でないテストコードを書きたいときはどうするの？

この記事で想定しているのは「ごく単純なテストコード」です。
一方でテスト対象のコードによっては「単純でないテストコード」が必要になる場合もあり得ます。
そういう場合はどうしたらよいのでしょうか？

僕の答えは「自分が一番使い慣れているテスティングフレームワークや自分が一番気に入っているテスティングフレームワークを使えば良い」です。

Test::Unitにしろ、Minitestにしろ、RSpecにしろ、それぞれ一長一短がありますし、人の好みもいろいろと分かれます。
なので、「みんなxxxを使え」とひとまとめに結論を出すことはできません。

ちなみに僕の場合はRSpecを使います。

- 一番使い慣れているので迷わず使える
- 標準で便利ないろいろな機能が入っている（gemであれこれ機能追加しなくてよい）
- 公式ドキュメントや書籍、ネット上の情報等が豊富

といった理由からRSpecをメインで使い続けています。

## まとめ
というわけでこの記事ではRuby標準のテスティングフレームワークを使って、テストコードを書く方法を紹介しました。

「テストコード ＝ 難しそうだから自分には無理」と思っている初心者の方も多いかもしれませんが、まずは一番簡単な `assert_equal (期待値), (実際の値)` だけでも書いてみるのがいいかなと思います。

テストコードを使えば「putsを使って毎回目視で確認」なんてするよりも、高速で確実に実行結果を検証することができます。

また、テストコードを書いておけば他の人も「このメソッドを呼ぶと何が起きるのか」を理解しやすくなります。

Rubyの場合、特別なgemをインストールしなくてもテストコードが書けるようになっているので、まだ書いたことがない人は一度トライしてみましょう！