Rubyは動的型付け言語であり、コンパイル時ではなく実行時に型が決まります。これは柔軟で高速な開発を可能にする反面、型の不一致などによる単純なミスが実行時まで検出されにくいという特性も持ちます。
そのため、Rubyコミュニティでは「テストは文化」と言われるほど、自動化されたテストを書くことが重視されます。テストは、コードが期待通りに動作することを保証するだけでなく、未来の自分や他の開発者がコードをリファクタリング(修正・改善)する際の「安全網」として機能します。
この章では、Rubyに標準で添付されているテスティングフレームワーク「Minitest」を使い、テストの基本的な書き方と文化を学びます。
Minitestは、Rubyに標準で含まれている(=別途インストール不要)軽量かつ高速なテストフレームワークです。
Ruby on Railsなどの主要なフレームワークもデフォルトでMinitestを採用しており、Rubyのエコシステムで広く使われています。(RSpecという、よりDSL(ドメイン固有言語)ライクに記述できる人気のフレームワークもありますが、まずは標準のMinitestを理解することが基本となります。)
Minitestは、Minitest::Test を継承する「Unitスタイル」と、describe ブロックを使う「Specスタイル」の2種類の書き方を提供しますが、この章では最も基本的なUnitスタイルを学びます。
早速、簡単なクラスをテストしてみましょう。
まず、テスト対象となる簡単な電卓クラスを作成します。
Rubyの規約では、テストファイルは test_ プレフィックス(例: test_calculator.rb)または _test.rb サフィックス(例: calculator_test.rb)で作成するのが一般的です。ここでは test_calculator.rb を作成します。
テストファイルは、以下の要素で構成されます。
require 'minitest/autorun'
require_relative 'ファイル名'
calculator.rb)を読み込みます。class クラス名 < Minitest::Test
Minitest::Test を継承します。def test_メソッド名
test_ で始まるメソッドを定義します。これが個々のテストケースとなります。ターミナルで、作成したテストファイルを実行します。
ruby test_calculator.rb実行結果のサマリに注目してください。
.(ドット): テストが成功(Pass)したことを示します。2 runs, 2 assertions: 2つのテスト(test_addition と test_subtraction)が実行され、合計2回のアサーション(assert_equal)が成功したことを意味します。0 failures, 0 errors: 失敗もエラーもありません。もしテストが失敗すると、F(Failure)や E(Error)が表示され、詳細なレポートが出力されます。
アサーション(Assertion = 表明、断言)は、「この値はこうあるべきだ」と検証するためのメソッドです。Minitestは Minitest::Test を継承したクラス内で、様々なアサーションメソッドを提供します。
最もよく使うアサーションです。「期待値(expected)」と「実際の結果(actual)」が == で等しいことを検証します。
⚠️ 注意: 引数の順序が重要です。**1番目が「期待値」、2番目が「実際の結果」**です。逆にすると、失敗時のメッセージが非常に分かりにくくなります。
test が true(またはtrueと評価される値)であることを検証します。偽(false または nil)の場合は失敗します。
assert の逆です。test が false または nil であることを検証します。
obj が nil であることを検証します。
ブロック { ... } を実行した結果、指定した例外(Exception)が発生することを検証します。
これは、意図したエラー処理が正しく動作するかをテストするのに非常に重要です。
ruby test_calculator_errors.rbTDD (Test-Driven Development) は、機能のコードを書く前に、まず失敗するテストコードを書く開発手法です。TDDは以下の短いサイクルを繰り返します。
Calculator クラスに、multiply(掛け算)メソッドをTDDで追加してみましょう。
まず、test_calculator.rb に multiply のテストを追加します。
この時点で calculator.rb に multiply メソッドは存在しません。テストを実行します。
ruby test_calculator_tdd.rb期待通り、NoMethodError でテストがエラー (E) になりました。これが「Red」の状態です。(Failure (F) はアサーションが期待と違った場合、Error (E) はコード実行中に例外が発生した場合を指します)
次に、calculator.rb に以下のように multiply メソッドを実装し、テストをパス(Green)させます。
class Calculator
def add(a, b)
a + b
end
def subtract(a, b)
a - b
end
# 2. Green: テストを通す最小限の実装
def multiply(a, b)
a * b
end
endcalculator.rb を編集し、再びテストを実行すると、以下のようにすべてのテストが成功します。「Green」の状態です。
$ ruby test_calculator_tdd.rb
...
Finished in ...
3 runs, 3 assertions, 0 failures, 0 errors, 0 skips今回は実装が非常にシンプルなのでリファクタリングの必要はあまりありませんが、もし multiply の実装が複雑になったり、他のメソッドとコードが重複したりした場合は、この「Green」の(テストが成功している)状態で安心してコードをクリーンアップします。
TDDは、この「Red -> Green -> Refactor」のサイクルを高速で回すことにより、バグの少ない、メンテンスしやすいコードを堅実に構築していく手法です。
require 'minitest/autorun' し、Minitest::Test を継承します。test_ プレフィックスで定義します。assert_equal(期待値, 実際の結果) が最も基本的なアサーションです。assert (true検証), refute (false検証), assert_raises (例外検証) などもよく使われます。Minitest::Test を使って、Rubyの組み込みクラスである String の動作をテストする test_string.rb を作成してください。以下の2つのテストメソッドを実装してください。
test_string_length: "hello" の length が 5 であることを assert_equal で検証してください。test_string_uppercase: "world" を upcase した結果が "WORLD" であることを assert_equal で検証してください。ruby test_string.rbTDDの「Red -> Green」サイクルを体験してください。
User クラスに first_name と last_name を渡してインスタンス化し、full_name メソッドを呼ぶと "First Last" のように連結された文字列が返ることを期待するテスト test_full_name を含む test_user.rb を先に作成してください。(この時点では user.rb は空か、存在しなくても構いません)user.rb に User クラスを実装してください。(initialize で名前を受け取り、full_name メソッドで連結します)ruby test_user.rb