test-unitの導入から書き方・動かし方のじぶんのやりかたメモ

最近仕事ではRSpecばかりだし趣味でRuby最近あまり書いてなくてよく忘れがちなのでメモ。
gemで用いる場合を想定しています。

## 導入

### gemspec

`Gem::Specification.new`に渡すブロック内に以下を追加。（`spec`はブロック引数）

```ruby
spec.add_development_dependency("test-unit")
spec.add_development_dependency("test-unit-notify")
```

スタブとかモックを使いたくなったら以下も追加する。

```ruby
spec.add_development_dependency("test-unit-rr")
```

### Rakefile

こんな感じのを追加する。（ファイル内に出てくるrun-test.rbについては後述）

```Rakefile
task :default => :test

desc "Run tests"
task :test do
  ruby("test/run-test.rb")
end
```

defaultを`:test`にしているのはCIのときに便利だからです。（Travis-CIとかはデフォルトで `rake` を実行するため）

### test/run-test.rb

https://github.com/yoshihara/test-unit-night/blob/master/lesson1/test/run-test.rb を使う。実行権限を付けておくと便利（後述）

gemspecに書いたライブラリを増やしたらその都度真ん中あたりしている`require`を増やすのをわすれない。

実行権限を付けておくと、`$ ./test/run-test.rb` と実行すれば引数で実行するテストを絞りこんだり色々できる。詳しくは `-h` オプションでみれる。よく使うのは `-t`と `-n`で、それぞれテストクラス名（後述）とテストメソッド名（後述）で実行するテストを絞り込める。正規表現が使えるので適当に `-t /User/` とか渡すとユーザー関連のテストが実行できたりする。

## テストファイルの置き方

1. 置き場所→`test/`ディレクトリ配下
  - それ以降のディレクトリの掘り方は実装（`lib/`配下）と合わせる（`lib/user.rb`のテストなら`test/test-user.rb`）
2. 名前→`test_hoge.rb` とか
  - とりあえず`test`をつける（うっかり`hoge_test.rb` とかにしてしまったこともある...）
  - `hoge`はテスト対象クラスのスネークケース

## テストの定義の仕方

テストファイル内でテストクラスを決まった形で定義し、その中で決まった形でメソッドを定義するとそれがテストとなる。（便宜上テストメソッドとよぶ）

1. テストクラス名→`HogeTest`
  - `Hoge` は大体テスト対象のクラス名（`User`のテストなら`TestUser`)
  - 後述するサブクラスを作った場合、サブクラス名は状態を示す感じにする。（RSpecでいうところのcontextっぽい感じ）
2. 継承クラス→`Test::Unit::TestCase`を継承する
3. テストメソッド名→`test_fuga`
  - `Test::Unit::TestCase`を継承しているクラスの`test_`からはじまるメソッドがテストとして使われる
  - なので、一回`Test::Unit::TestCase`を継承したクラス内で`class TestPiyo < self`とかやると、`TestPiyo`内で定義されたテストメソッドも実行される

## アサーションメソッド

だいたい `asert_foo(expected, actual)` もしくは `assert_bar(actual)` の形。`assert` はブロックを渡す。
とりあえず`assert_equal`だけ覚えておけばなんとかなる。
`assert_raise`は処理の内容をチェックするのでブロックを渡す。

## 準備系メソッド

- `setup`（各テストの前）
- `teardown`（各テストの後）
- `startup`（全テストの最初だけ）
- `shutdown`（全テストの最後だけ）

それぞれ、`def setup`みたいにメソッド定義でも書けるし、`setup do...end`のようにブロックも渡せる。（当然、サブクラスでのメソッド定義の場合は親のクラスでの定義を上書きするので気をつける）

## データ駆動

指定したデータそれぞれに対して同じテストを実行するときに便利な機能。

参考：http://www.clear-code.com/blog/2013/1/23.html

使うには、実行したいテストメソッドの直前に`data`メソッドを呼ぶ。引数の取り方は3パターンあるけど、大体2つ目のハッシュを渡すパターンをよく使う気がする。

- `data(label, using_data)` を複数書く
- `data(label1 => using_data1, ...)` （Hashオブジェクトを渡す）
- `data({label1 => using_data1, ...}となるHashオブジェクトを返すブロック)`

`data`メソッドを定義したあとのテストメソッドで引数を設定すると、その引数に`using_data`が示すオブジェクトが渡ってくる。（`label`がどこで使われるかは後述する。）`label`と`using_data`の組だけテストメソッドが実行される。

実際に`data`メソッドを使うときは次のようになる。次の例だと、テストメソッド`test_size`は、`[2, "ja"]` が引数の場合と`[8, "japanese"]`が引数の時の2回実行される。

```ruby
data("ja"       => [2, "ja"],
     "japanese" => [8, "japanese"])
def test_size(data)
  # p data # => [2, "ja"] or [8, "japanese"]
  expected, actual = data
  assert_equal(expected, actual.size)
end
```

なお、`label`がどこで使われるかというと、テストが失敗した時の表示で使われる。次は、↑のテストメソッドで`[8, "japanese"]`のところを`[9, "japanese"]`（期待する文字数が間違っている）として実行した場合のコンソール出力になる。diffの辺りがdiffっぽくないけど、実際は色づけされる。
3行目の`test_size`の後ろに、失敗したときのデータに紐付いた`label`が表示されている。

```
===============================================================================
Failure:
test_size[japanese](UserTest)
/Users/yoshihara/everyleaf/test-unit-night/lesson1/test/user-test.rb:25:in `test_size'
     22:   def test_size(data)
     23:     # p data # => [2, "ja"] or [8, "japanese"]
     24:     expected, actual = data
  => 25:     assert_equal(expected, actual.size)
     26:   end
     27:
     28:   def test_full_name_ja
<9> expected but was
<8>

diff:
? 9
? 8
===============================================================================
```

そのため、`label`はテスト失敗時にわかりやすくなるような名前をつける。actualを示すようなのがわかりやすい。（今回はactualのデータそのままだけど例えば空配列を渡すときは`"empty array"`とかにするとわかりやすそう）

## スタブとかモックとか

`test-unit-rr`のrequireが必須になる。
`test-unit-rr`は内部で[rr](https://github.com/rr/rr)を使っているのでその記法がそのまま使える。README見ればだいたい分かる。下記はメモ。

```ruby
stub(object).method(arguments) { return_value }
mock(object).method(arguments) { return_value }
```

## その他

上記以外は普通にRubyでクラスを定義するのと変わりないので、たとえば便利メソッドをprivateで定義したり、さらにそれをmoduleに切り出してincludeして複数テストクラスで使ったりもできる。

## おわり

以上です。他にも書いたほうがいいものがあったら追記する予定です。