Minitestは、Ruby向けの軽量なテストフレームワークです。Rubyの標準ライブラリに含まれており、Rubyのバージョン1.9以降で利用可能です。Minitestは、テスト駆動開発（TDD）や振る舞い駆動開発（BDD）などのソフトウェア開発手法を支援するために使用されます。
Minitestは、シンプルで直感的な構文を提供し、Rubyの組み込み機能との親和性が高いため、学習コストが比較的低いです。また、高速で効率的なテストランナーを備えており、テストスイート全体の実行速度が速いという利点もあります。
Minitestは、次のような主な機能を提供しています：
テストケースの定義: テストケースは、テスト対象のコードの特定の振る舞いや機能をテストするための単位です。
アサーション: アサーションを使用して、期待される結果を確認します。たとえば、特定の条件が真であることを確認する assert メソッドがあります。
テストランナー: テストランナーは、定義されたテストケースやテストメソッドを実行し、結果を収集して報告します。
テストフィクスチャ: テストフィクスチャを使用して、テストケースの前後に特定の状態を設定したり解放したりします。これにより、テストの再現性と信頼性が向上します。
Minitestを使用することで、Rubyプログラムの品質や安定性を向上させるための効果的なテストを作成することができます。
----
インストール: MinitestはRubyの bundled gem であり、Rubyをインストールすると含まれています。
テストファイルの作成: テストを実行するためのファイルを作成します。通常、テストファイルの名前は *_test.rb とします。例えば、calculator_test.rb のような名前が一般的です。
アサーションの使用: テストケース内で、テストの期待結果をアサーションを使用して記述します。アサーションは、実際の結果が期待される結果と一致していることを確認します。
def test_addition
  assert_equal 5, 2 + 3
end
この場合、2 + 3 の結果が 5 と等しいことを確認しています。
テストの実行: テストファイルを実行して、テストを実行します。通常、以下のコマンドを使用します。
ruby calculator_test.rb
または、rake や rake test を使用してテストを実行することもできます。
rake test
テスト結果の確認: テストの実行が完了すると、テスト結果が表示されます。各テストケースが成功したか、失敗したか、またはエラーが発生したかなどの情報が表示されます。
以下は、Minitestで利用可能なアサーションメソッドとヘルパーメソッドです。
これらのアサーションメソッドとヘルパーメソッドを使って、テストコードを記述してコードの動作を確認できます。
assert(test, msg = nil)とrefute(test, msg = nil)の関係のように、assert_*とrefute_*では確認する論理が逆になります。
MinitestにはTestUnitスタイルのテストとRSpecのようなSpecスタイルのテストの両方を記述できます。これらのスタイルにはいくつかの違いがあります。
構文: TestUnitスタイルでは、テストクラスを作成し、その中にテストメソッドを定義します。テストメソッドの名前は通常、test_で始まります。
require 'minitest'
class IntegerArithmeticTest < Minitest::Test
  def test_add
    assert_equal 2, 1 + 1
  end
  def test_sub
    assert_equal 0, 1 - 1
  end
  def test_mul
    assert_equal 6, 2 * 3
  end
  def test_mul
    assert_equal 6, 2 * 3
  end
  def test_div
    assert_equal 1, 3 / 2
  end
  def test_zerodiv
    assert_raises(ZeroDivisionError) { 3 / 0 }
  end
  def test_zerozerodiv
    assert_raises(ZeroDivisionError) { 0 / 0 }
  end
end
Minitest.run
概念の直接性: TestUnitスタイルは比較的直接的でシンプルです。テストクラスとテストメソッドを作成し、アサーションメソッドを使用して期待値と実際の値を比較します。
テストの構造: テストはクラスとメソッドの階層構造を持ちます。これは、テストの整理や分類に便利です。
構文: Specスタイルでは、describeとitを使用してテストのグループ化と記述を行います。describeはテストのグループを作成し、itは特定のテストケースを定義します。
require 'minitest/spec'
describe '整数演算' do
  it '加算' do
    expect(1 + 1).must_equal 2
  end
  it '減算' do
    expect(1 - 1).must_equal 0
  end
  it '乗算' do
    expect(2 * 3).must_equal 6
  end
  describe '除法' do
    it '除算' do
      expect(3 / 2).must_equal 1
    end
    it 'ゼロ除算' do
      expect { 3 / 0 }.must_raise ZeroDivisionError
    end
    it 'ゼロゼロ除算' do
      expect { 0 / 0 }.must_raise ZeroDivisionError
    end
  end
end
Minitest.run
ドメイン特化言語(DSL): SpecスタイルはDSLの特徴を持ち、テストが自然言語に近い形で書かれることがあります。must, wont, should, expectなどのメソッドがアサーションを表します。
期待される動作の表現: Specスタイルでは、テストが期待される動作をより詳細に表現することが一般的です。これにより、テストコードがより読みやすく、テストの目的が明確になります。
両方のスタイルは使いやすく、プロジェクトやチームの好みに応じて選択できます。一般的には、テストの構造をより詳細に表現する必要がある場合はSpecスタイルが好まれ、シンプルな場合はTestUnitスタイルが選択されることがあります。
Minitest/Testは、Minitest gemの中核をなすテストライブラリで、xUnit (JUnit、RUnit) スタイルのテストを書くためのツールです。
Minitest/Testでは、Minitest::Testを継承したテストクラスを作成し、そのクラス内にテストメソッドを定義します。テストメソッド名は、test_で始める必要があります。
require 'minitest/autorun'
class ArrayTest < Minitest::Test
  def test_reverse
    array = [1, 2, 3]
    reversed = array.reverse
    assert_equal [3, 2, 1], reversed
  end
end
この例では、ArrayTestクラスを定義し、そこにtest_reverseというテストメソッドを書いています。assert_equalメソッドを使って、実際の結果と期待される結果を比較しています。
Minitest/Testには、さまざまなアサーションメソッドが用意されています。一般的に使われるものは以下のとおりです。
その他にも多くのアサーションメソッドが用意されており、オブジェクトの種類やパターンマッチなど、さまざまなケースをカバーできます。
テストの前後で実行したい処理がある場合は、setupメソッドとteardownメソッドをオーバーライドして記述します。
require 'minitest/autorun'
class DatabaseTest < Minitest::Test
  def setup
    @db = Database.new
  end
  def teardown
    @db.close
  end
  def test_query
    result = @db.query('SELECT * FROM users')
    assert_equal 3, result.count
  end
end
この例では、各テストケースの前にDatabaseオブジェクトをインスタンス化し、テストケースの後にデータベース接続を閉じています。setupとteardownを適切に使うことで、テストの再現性と信頼性が高まります。
Minitestにはテストランナーが組み込まれているので、テストスクリプトを単に実行するだけでテストを実行できます。
require 'minitest/autorun'
class MyTest < Minitest::Test
  # ...
end
テストの実行:
$ ruby my_test.rb
また、rake（Rakefile経由）、ruby -Ilib:test（ロードパスを追加）などの方法でもテストを実行できます。
Minitest/Testは、Minitest gemに付属する本来のテストライブラリです。xUnitスタイルの記述でテストを書くことができ、アサーションメソッドやセットアップ/ティアダウンメソッドなどを活用してテストを作成します。小規模から大規模まで幅広いプロジェクトで使われており、信頼性の高いテストを書くことができます。Railsなどのフレームワークのテストでも利用されています。
[https://paiza.io/projects/W8NYKFxvzL4qA_APEtTJ2w?language=ruby gcd.rb]
def gcd(m, n) = n.zero? ? m : gcd(n, m % n)
require 'minitest/autorun'
class TestGCD < Minitest::Test
  def test_gcd_with_coprime_numbers
    assert_equal 1, gcd(3, 7)
    assert_equal 1, gcd(10, 21)
    assert_equal 1, gcd(8, 13)
  end
  
  def test_gcd_with_non_coprime_numbers
    assert_equal 2, gcd(4, 6)
    assert_equal 3, gcd(15, 9)
    assert_equal 6, gcd(24, 18)
  end
  
  def test_gcd_with_same_numbers
    assert_equal 5, gcd(5, 5)
    assert_equal 10, gcd(10, 10)
    assert_equal 17, gcd(17, 17)
  end
  
  def test_gcd_with_one_zero
    assert_equal 5, gcd(5, 0)
    assert_equal 10, gcd(0, 10)
    assert_equal 17, gcd(17, 0)
    assert_equal 1, gcd(0, 1)
  end
  
  def test_gcd_with_both_zero
    assert_equal 0, gcd(0, 0)
  end
  def test_gcd_with_large_number
    assert_equal 3, gcd(2**99+1, 2**199+1)
  end
end
最初に、最大公約数を再帰的に計算するためのメソッドを定義しています。
ユークリッドの互除法を使いました。
再帰的な呼び出しを行い、nが0になるまでmとnの最大公約数を求めます。
Rubyの新しい構文である "def method = expression" を使用して、1行でメソッドを定義しています。
 
次に、Minitestのテストケースが定義されています。
TestGCD クラスは Minitest::Test を継承しており、各テストメソッドで最大公約数メソッドを呼び出して、その結果が期待通りであることを検証します。
各テストメソッド内では assert_equal を使用して、期待される値と実際の値を比較しています。たとえば、assert_equal 1, gcd(3, 7) は、3と7の最大公約数が1であることを検証しています。
このようにして、再帰を利用して最大公約数を計算し、Minitestを使用してテストすることができます。
二分木クラスを定義
[https://paiza.io/projects/bYy5k6Bqq7-Y2Ykih4hPzA?language=ruby binarytree.rb]
class BinaryTree
  include Enumerable # Enumerableモジュールを含める
  # 二分木のノード
  TreeNode = Struct.new(:value, :left, :right)
  def self.new_node(*args) = TreeNode.new(*args)
  # 新しいツリーを作成
  def initialize(*_args)
    @root = nil
  end
  attr_accessor :root
  def height(node = @root) = node.nil? ? 0 : 1 + [height(node.left), height(node.right)].max
  def search(key, _node = @root)
    raise TypeError, "Invalid value: #{key.inspect}" unless key.respond_to?(:<)
    raise TypeError, "Invalid value: #{key.inspect}" if key.is_a?(Numeric) && !key.finite?
    any? { |i| i == key }
  end
  # 中間順序（inorder）で木を走査し、各ノードの値をブロックに渡します。
  def each(node = @root, █)
    return to_enum(__method__, node) unless block
    def core(node, █)
      return if node.nil?
      core(node.left, █)
      yield node.value
      core(node.right, █)
    end
    core(node, █)
    self
  end
  # ツリーの文字列表現を返します。
  #
  # Returns ツリーを表す文字列。
  def to_s =  "(#{to_a.join ' '})"
  # ツリーのデバッグ用表現を返します。
  #
  # Returns デバッグ用表現を表す文字列。
  def inspect ="#{self.class}(#{to_a.join ', '})"
end
require 'minitest'
class BinaryTreeTest < Minitest::Test
  def initialize(*args)
    super(*args)
    @target_class = BinaryTree
  end
  def setup
    @tree = @target_class.new
  end
  def test_initialization
    assert_nil @tree.root
  end
  def test_to_s
    assert_equal '()', @tree.to_s
  end
  def test_inspect
    assert_equal "#{@target_class}()", @tree.inspect
  end
  def test_set_value
    @tree.root = @tree.class.new_node(:+)
    assert_equal "#{@target_class}(+)", @tree.inspect
  end
  def test_height_with_empty_tree
    assert_equal 0, @tree.height
  end
  def test_height_with_single_node
    @tree.root = BinaryTree.new_node(5)
    assert_equal 1, @tree.height
  end
  # 空の木のテスト
  def test_empty_tree
    assert_equal '()', @tree.to_s
    assert_equal "#{@target_class}()", @tree.inspect
  end
  # 1つのノードしか持たない木のテスト
  def test_single_node_tree
    @tree.root = @tree.class.new_node(:+)
    assert_equal '(+)', @tree.to_s
    assert_equal "#{@target_class}(+)", @tree.inspect
  end
  def test_add_left
    @tree.root = @tree.class.new_node(:+)
    @tree.root.left = @tree.class.new_node(10)
    assert_equal "#{@target_class}(10, +)", @tree.inspect
  end
  def test_height
    assert_equal 0, @tree.height
    @tree.root = @tree.class.new_node(:+)
    assert_equal 1, @tree.height
    @tree.root.left = @tree.class.new_node(10)
    assert_equal 2, @tree.height
    @tree.root.right = @tree.class.new_node(:*)
    assert_equal 2, @tree.height
    @tree.root.right.left = @tree.class.new_node(20)
    assert_equal 3, @tree.height
    @tree.root.right.right = @tree.class.new_node(30)
    assert_equal 3, @tree.height
    assert_equal '(10 + 20 * 30)', @tree.to_s
    assert_equal "#{@target_class}(10, +, 20, *, 30)", @tree.inspect
  end
  def test_each
    @tree.root = @tree.class.new_node(:+)
    @tree.root.left = @tree.class.new_node(10)
    @tree.root.right = @tree.class.new_node(:*)
    @tree.root.right.left = @tree.class.new_node(20)
    @tree.root.right.right = @tree.class.new_node(30)
    expected = '10 + 20 * 30 '
    assert_equal(expected, capture_stdout { @tree.each { |value| print "#{value} " } })
    assert_equal(expected, capture_stdout do
      enum = @tree.each
      enum.each do |value|
        print "#{value} "
      end
    end)
  end
  def test_search_with_existing_value
    @tree.root = @target_class.new_node(5)
    assert @tree.search(5)
  end
  def test_search_with_non_existing_value
    @tree.root = @target_class.new_node(5)
    refute @tree.search(10)
  end
  def test_search_with_invalid_value
    @tree.root = @target_class.new_node(5)
    assert_raises(TypeError) { @tree.search(0.0 / 0.0) }
  end
  if $PROGRAM_NAME == __FILE__
    def make_tree
      @tree.root = @tree.class.new_node(:+)
      @tree.root.left = @tree.class.new_node(10)
      @tree.root.right = @tree.class.new_node(:*)
      @tree.root.right.left = @tree.class.new_node(20)
      @tree.root.right.right = @tree.class.new_node(30)
    end
    def test_search_no_exist
      make_tree
      refute @tree.search(0)
      refute @tree.search(1)
      refute @tree.search(100)
      refute @tree.search(123.456)
      assert_raises(TypeError) { @tree.search([1, 2, 3]) }
      refute @tree.search({ a: 1 })
    end
    def test_search_right
      make_tree
      assert @tree.search(:*)
    end
    def test_search
      make_tree
      assert @tree.search(:+)
      assert @tree.search(10)
      assert @tree.search(20)
      assert @tree.search(30)
      assert @tree.search(30.0)
    end
  end
  private
  # 標準出力をキャプチャして文字列として返す
  def capture_stdout
    original_stdout = $stdout
    $stdout = StringIO.new
    yield
    $stdout.string
  ensure
    $stdout = original_stdout
  end
end
Minitest.run if $PROGRAM_NAME == __FILE__
二分木クラスを継承し二分探索木クラスを定義
[https://paiza.io/projects/IdDgQWoLeI89wOEMTCywEA?language=ruby binarytree.rb]
require_relative 'binarytree'
class BinarySearchTree < BinaryTree
  # 新しい二分探索木を作成します。
  #
  # @param args [Array] 挿入する要素の配列
  # @yield [element] ブロックが与えられた場合、各要素に対してブロックを実行し、その結果を挿入します。
  # @yieldparam element [Object] 要素
  def initialize(*args)
    @root = nil
    case args
    in [Array(*ary)]
      if block_given?
        ary.each { insert yield(_1) }
      else
        ary.each { insert _1 }
      end
    in []
    else
      raise ArgumentError, "#{self.class}#initialize: #{args.inspect}"
    end
  end
  # 二分探索木に新しい値を挿入します。
  #
  # @param value [Object] 挿入する値
  # @return [BinarySearchTree] 自身のインスタンス
  def insert(value, node = @root)
    raise TypeError, "Invalid value: #{value.inspect}" unless value.respond_to?(:<)
    raise TypeError, "Invalid value: #{value.inspect}" if value.is_a?(Numeric) && !value.finite?
    @root = insert_recursive(value, node)
    # @root = insert_iterative(value, node)
    self
  end
  # 指定されたキーを持つ要素が存在するかどうかを返します。
  #
  # @param key [Object] 検索するキー
  # @return [Boolean] 指定されたキーを持つ要素が存在する場合はtrue、それ以外の場合はfalse
  def search(key, node = @root)
    raise TypeError, "Invalid value: #{key.inspect}" unless key.respond_to?(:<)
    raise TypeError, "Invalid value: #{key.inspect}" if key.is_a?(Numeric) && !key.finite?
    search_recursive(key, node)
    # search_iterative(key, node)
  end
  # 指定されたキーを持つ要素を削除します。
  #
  # @param key [Object] 削除する要素のキー
  # @return [BinarySearchTree] 自身のインスタンス
  def delete(key, node = @root)
    raise TypeError, "Invalid value: #{key.inspect}" unless key.respond_to?(:<)
    raise TypeError, "Invalid value: #{key.inspect}" if key.is_a?(Numeric) && !key.finite?
    @root = delete_node(key, node)
    self
  end
  protected
  # 二分探索木に値を再帰的に挿入します。
  #
  # @param node [Node, nil] 現在のノード
  # @param value [Object] 挿入する値
  # @return [Node] 挿入後のノード
  def insert_recursive(value, node)
    return self.class.new_node(value) if node.nil?
    case value <=> node.value
    when -1 then node.left = insert_recursive(value, node.left)
    when 1 then node.right = insert_recursive(value, node.right)
    when 0 # sum value
    else raise TypeError, value.inspect
    end
    node
  end
  def insert_iterative(value, node)
    return Node.new(value) if node.nil?
    prev = nil
    temp = node
    until temp.nil?
      prev = temp
      temp = case value <=> temp.value
             when -1 then temp.left
             when +1 then temp.right
             when 0 then break
             else raise TypeError, value.inspect end
    end
    unless prev.nil?
      case value <=> prev.value
      when -1 then prev.left = Node.new(value)
      when +1 then prev.right = Node.new(value)
      when 0 # break
      else raise TypeError, value.inspect
      end
    end
    node
  end
  def search_recursive(key, node)
    return false if node.nil?
    case node.value <=> key
    when -1 then search_recursive(key, node.left)
    when +1 then search_recursive(key, node.right)
    when 0 then true
    else raise TypeError, "#{self.class}#search_recursive: #{key.inspect}"
    end
  end
  def search_iterative(key, node)
    until node.nil?
      node = case node.value <=> key
             when -1 then node.left
             when +1 then node.right
             when 0 then return true
             else raise TypeError, "#{self.class}#search_iterative: #{key.inspect}"
             end
    end
    false
  end
  def delete_node(key, node)
    return node if node.nil?
    case key <=> node.value
    when -1
      node.left = delete_node(key, node.left)
      return node
    when 1
      node.right = delete_node(key, node.right)
      return node
    when 0 # sum value
    else raise TypeError, value.inspect
    end
    if node.left.nil?
      return node.right
      elif node.right.nil?
      root.left
    else
      succParent = node
      succ = node.right
      while succ.left
        succParent = succ
        succ = succ.left
      end
      if succParent != node
        succParent.left = succ.right
      else
        succParent.right = succ.right
      end
      node.value = succ.value
      node
    end
  end
end
def BinarySearchTree(args) = BinarySearchTree.new(args)
require 'minitest'
class BinarySearchTreeTest < BinaryTreeTest
  def initialize(*args)
    super(*args)
    @target_class = BinarySearchTree
  end
  def setup
    @tree = @target_class.new
  end
  # 配列を使ってコンストラクタをテストします。
  def test_constructor_with_array
    @tree = @target_class.new([7, 5, 8])
    assert_equal '(5 7 8)', @tree.to_s
  end
  # 配列とブロックを使ってコンストラクタをテストします。
  def test_constructor_with_array_with_block
    @tree = @target_class.new([7, 5, 8]) { |i| 2 * i + 1 }
    assert_equal '(11 15 17)', @tree.to_s
    assert_equal "#{@target_class}(11, 15, 17)", @tree.inspect
  end
  # 文字列を使ってコンストラクタをテストします。
  def test_constructor_with_array
    assert_raises(ArgumentError) { _ = BinarySearchTree.new('abc') }
  end
  def test_inspect
    @tree.insert(1)
    assert_equal "#{@target_class}(1)", @tree.inspect
    @tree.insert(3)
    assert_equal "#{@target_class}(1, 3)", @tree.inspect
    @tree.insert(2)
    assert_equal "#{@target_class}(1, 2, 3)", @tree.inspect
    @tree.insert(0)
    assert_equal "#{@target_class}(0, 1, 2, 3)", @tree.inspect
  end
  # insertメソッドが要素をツリーに追加することをテストします。
  def test_insert_adds_element_to_tree
    @tree.insert(1)
    assert_equal '(1)', @tree.to_s
    @tree.insert(2)
    assert_equal '(1 2)', @tree.to_s
    @tree.insert(-1)
    assert_equal '(-1 1 2)', @tree.to_s
    refute @tree.search(0)
    assert @tree.search(1)
    begin
      assert_equal 'NaN', @tree.search(0.0 / 0.0)
    rescue StandardError
      'NaN'
    end
    @tree.delete 1
    assert_equal '(-1 2)', @tree.to_s
    @tree.delete 0
    assert_equal '(-1 2)', @tree.to_s
    @tree.delete(-2)
    assert_equal '(-1 2)', @tree.to_s
    @tree.delete 3
    assert_equal '(-1 2)', @tree.to_s
    @tree.delete(-1)
    assert_equal '(2)', @tree.to_s
    @tree.delete 2
    assert_equal '()', @tree.to_s
  end
  def test_height
    [10, 5, 15, 3, 7, 12, 18].each { @tree.insert _1 }
    assert_equal 3, @tree.height
    @tree.insert 2
    assert_equal 4, @tree.height
    @tree.insert 1
    assert_equal 5, @tree.height
    @tree.insert 0
    assert_equal 6, @tree.height
    @tree.insert(-1)
    assert_equal 7, @tree.height
    @tree.insert(-2)
    assert_equal 8, @tree.height
    assert_equal "#{@target_class}(-2, -1, 0, 1, 2, 3, 5, 7, 10, 12, 15, 18)", @tree.inspect
  end
  def test_each
    [10, 5, 15, 3, 7, 12, 18].each { @tree.insert _1 }
    expected_output = '3 5 7 10 12 15 18 '
    @tree.insert 10
    assert_equal(expected_output, capture_stdout { @tree.each { |value| print "#{value} " } })
    assert_equal(expected_output, capture_stdout do
      enum = @tree.each
      enum.each do |value|
        print "#{value} "
      end
    end)
  end
  # 二分探索木の要素を削除するテスト
  def test_delete_removes_element_from_tree
    @tree.insert(5)
    @tree.insert(3)
    @tree.insert(7)
    @tree.insert(2)
    @tree.insert(4)
    @tree.insert(6)
    @tree.insert(8)
    # 二分探索木の要素を削除する
    @tree.delete(3)
    # 期待される結果: (2 4 5 6 7 8)
    assert_equal '(2 4 5 6 7 8)', @tree.to_s
  end
  # 存在しない要素を削除するテスト
  def test_delete_non_existent_element
    @tree.insert(5)
    @tree.insert(3)
    @tree.insert(7)
    # 存在しない要素を削除する
    @tree.delete(10)
    # 期待される結果: (3 5 7)
    assert_equal '(3 5 7)', @tree.to_s
  end
  def test_search; end
  # NaNを挿入すると例外が発生しすることを確認するテスト
  def test_inserting_nan_does_raise_exception
    assert_raises(TypeError) { @tree.insert(Float::NAN) }
  end
  def test_serching_nan_does_raise_exception
    assert_raises(TypeError) { @tree.search(Float::NAN) }
  end
  def test_deleting_nan_does_raise_exception
    assert_raises(TypeError) { @tree.delete(Float::NAN) }
  end
  def test_inserting_inf_does_raise_exception
    assert_raises(TypeError) { @tree.insert(Float::INFINITY) }
  end
  def test_serching_inf_does_raise_exception
    assert_raises(TypeError) { @tree.search(Float::INFINITY) }
  end
  def test_deleting_nil_does_raise_exception
    assert_raises(TypeError) { @tree.delete(Float::INFINITY) }
  end
  def test_inserting_nil_does_raise_exception
    assert_raises(TypeError) { @tree.insert(nil) }
  end
  def test_serching_nil_does_raise_exception
    assert_raises(TypeError) { @tree.search(nil) }
  end
  def test_deleting_inf_does_raise_exception
    assert_raises(TypeError) { @tree.delete(nil) }
  end
  def test_inserting_string_does_not_raise_exception
    assert_silent { @tree.insert('abc') }
  end
  def test_serching_string_does_not_raise_exception
    assert_silent { @tree.search('abc') }
  end
  def test_deleting_string_does_not_raise_exception
    assert_silent { @tree.delete('abc') }
  end
  def test_insert_type_missmatch
    assert_raises(TypeError) { @tree.insert('abc').insert(1) }
  end
  def test_insert_zero_and_Zero
    assert_silent { @tree.insert(0).insert(0.0) }
    assert_equal '(0)', @tree.to_s
  end
  def test_insert_strings
    assert_silent { @tree.insert('abc').insert('ab').insert('abs') }
    assert_equal '(ab abc abs)', @tree.to_s
  end
  def test_inserting_compilexes
    assert_raises(TypeError) { @tree.insert(Complex(0, 0)) }
  end
  def test_searching_compilexes
    assert_raises(TypeError) { @tree.search(Complex(0, 0)) }
  end
  def test_deleting_compilexes
    assert_raises(TypeError) { @tree.delete(Complex(0, 0)) }
  end
  # 要素がない場合に検索が正しく動作するかをテスト
  def test_search_on_empty_tree
    refute @tree.search(10)
  end
  # 要素が存在しない場合に検索が正しく動作するかをテスト
  def test_search_non_existent_element
    @tree.insert(5)
    @tree.insert(3)
    @tree.insert(7)
    refute @tree.search(10)
  end
  # 巨大な木を作るテスト
  def test_build_large_tree
    srand(19)
    n = 100_000
    @tree = @target_class.new((0...n).to_a.shuffle)
    assert_equal 43, @tree.height
    assert_equal n, @tree.count
  end
  private
  # 標準出力をキャプチャして文字列として返す
  def capture_stdout
    original_stdout = $stdout
    $stdout = StringIO.new
    yield
    $stdout.string
  ensure
    $stdout = original_stdout
  end
end
Minitest.run if $PROGRAM_NAME == __FILE__
Minitest/Specは、Minitest gem にバンドルされている、RSpecスタイルの記述方法を提供するライブラリです。RSpecのような自然言語に近い記述スタイルを使用することで、テストコードの可読性が向上します。
Minitest/Specでは、describeブロックを使ってテストの対象を記述し、その中にitブロックを使って個々のテストケースを記述します。
require 'minitest/spec'
describe 'Array' do
  describe '#reverse' do
    it 'reverses the order of elements' do
      array = [1, 2, 3]
      reversed = array.reverse
      expect(reversed).must_equal [3, 2, 1]
    end
  end
end
この例では、Array#reverseメソッドのテストを記述しています。describe 'Array'ブロックで、テストの対象をArrayクラスとしています。その中のdescribe '#reverse'ブロックでは、#reverseメソッドに関するテストをグループ化しています。最後にitブロック内に、実際のテストケースを記述しています。
Minitest/Specでは、RSpecスタイルの記述に合わせた独自のアサーションメソッドが提供されています。
expect(actual).must_equal expected
expect(actual).wont_equal unexpected
must_equalを使うとアサーションが成功した場合、wont_equalを使うと失敗した場合に成功するようになっています。同様にBooleanを期待する場合はmust_be :true?やwont_be :true?を使用します。
その他にも以下のようなアサーションメソッドが用意されています。
Minitest/Specでは、テストケースの前後で実行したい処理をbefore、after、aroundブロックで記述できます。
describe 'Something' do
  before do
    # 全てのテストケースの前に実行される
  end
  after do
    # 全てのテストケースの後に実行される
  end
  around do |tests|
    # テストケースの前後で実行される
    # blockを渡す
    tests.call
  end
  it 'does something' do
    # テストケース
  end
end
beforeブロックはテストケース実行前に、afterブロックはテストケース実行後に実行されます。aroundブロックはテストケース実行の前後に実行され、tests.callでテストケース自身を実行します。
これらのブロックは、テストデータのセットアップやテストが終わった後の後始末など、テストケースを実行する前後で行いたい処理を記述するのに便利です。
Minitest/Specを使うことで、RSpecに近い自然言語風の記述でテストを書くことができ、テストコードの可読性が高まります。同時に、Minitestの軽量さや高速な実行も継承しているため、プロジェクトの要件に合わせて使い分けができます。
はい、Minitest/Specではletやlet!を使ってインスタンス変数を初期化することができ、とても重要な機能です。
letを使うと、各テストケースの実行時にインスタンス変数を初期化するロジックを記述できます。初期化のオーバーヘッドを最小限に抑えつつ、DRY(Don't Repeat Yourself)なコードを書くことができます。
require 'minitest/spec'
describe Array do
  let(:array) { [1, 2, 3] }
  describe '#reverse' do
    it 'reverses the order of elements' do
      reversed = array.reverse
      expect(reversed).must_equal [3, 2, 1]
    end
  end
end
この例では、let(:array) { [1, 2, 3] }でインスタンス変数@arrayを初期化するロジックを定義しています。letブロック内のコードは、そのスコープ内の各テストケースが実行される度に評価されます。
つまり、上の例の場合、#reverseメソッドに関するすべてのテストケースで、@arrayは[1, 2, 3]を返すようになります。
let!はletに似ていますが、テストケースが実行される前に必ず実行されるという点が異なります。オブジェクトの初期化が重たい場合などに便利です。
require 'minitest/spec'
describe Database do
  let!(:db) { Database.new }
  describe '#query' do
    it 'can query the database' do
      result = db.query('SELECT * FROM users')
      expect(result).wont_be_empty
    end
  end
end
この例では、Databaseオブジェクトの初期化が重たい処理だと想定しています。let!(:db) { Database.new }によって、最初のテストケースが実行される前に@dbが初期化されるので、その後に続くすべてのテストケースで同じオブジェクトを使えます。
letやlet!を使うことで、DRYなコードを書けるだけでなく、各テストケースでのインスタンス変数の初期化ロジックを明示的に記述できるので、テストコードの可読性が高まります。
[https://paiza.io/projects/19D2Q9TLHptrSkr3--o0Cw?language=ruby gcd-spec.rb]
def gcd(m, n)
  raise ArgumentError, "Argument 1 must be integers" unless m.is_a?(Integer)
  raise ArgumentError, "Argument 2 must be integers" unless n.is_a?(Integer)
  def core(m, n) = n.zero? ? m.abs : gcd(n, m % n) 
  core(m, n)
end
require 'minitest/spec'
describe "gcd" do
  it "returns the greatest common divisor of two numbers" do
    assert_equal 3, gcd(9, 6)
    assert_equal 5, gcd(10, 15)
    assert_equal 1, gcd(7, 5)
  end
  it "returns the greatest common divisor of two numbers when one of the numbers is negative" do
    assert_equal 2, gcd(-6, 8)
    assert_equal 2, gcd(6, -8)
  end
  it "returns the number itself when one of the numbers is zero" do
    assert_equal 6, gcd(6, 0)
    assert_equal 8, gcd(0, 8)
    assert_equal 0, gcd(0, 0)
  end
  it "raises an ArgumentError when non-integer arguments are provided" do
    assert_raises(ArgumentError) { gcd(3.5, 7) }
    assert_raises(ArgumentError) { gcd("hello", 5) }
    assert_raises(ArgumentError) { gcd(10, []) }
  end
end
Minitest.run
[https://paiza.io/projects/z4ztowOqjlu-VkpHotY9Kg?language=ruby binarytree-spec.rb]
class BinaryTree
  include Enumerable # Enumerableモジュールを含める
  # 二分木のノード
  TreeNode = Struct.new(:value, :left, :right)
  def self.new_node(*args) = TreeNode.new(*args)
  # 新しいツリーを作成
  def initialize(*_args)
    @root = nil
  end
  attr_accessor :root
  def height(node = @root) = node.nil? ? 0 : 1 + [height(node.left), height(node.right)].max
  def search(key, _node = @root)
    raise TypeError, "Invalid value: #{key.inspect}" unless key.respond_to?(:<)
    raise TypeError, "Invalid value: #{key.inspect}" if key.is_a?(Numeric) && !key.finite?
    any? { |i| i == key }
  end
  # 中間順序（inorder）で木を走査し、各ノードの値をブロックに渡します。
  def each(node = @root, █)
    return to_enum(__method__, node) unless block
    def core(node, █)
      return if node.nil?
      core(node.left, █)
      yield node.value
      core(node.right, █)
    end
    core(node, █)
    self
  end
  # ツリーの文字列表現を返します。
  #
  # Returns ツリーを表す文字列。
  def to_s =  "(#{to_a.join ' '})"
  # ツリーのデバッグ用表現を返します。
  #
  # Returns デバッグ用表現を表す文字列。
  def inspect ="#{self.class}(#{to_a.join ', '})"
end
require 'minitest/spec'
describe BinaryTree do
  let(:target_class) { BinaryTree }
  let(:tree) { target_class.new }
  describe 'initialization' do
    it 'must have nil root' do
      expect(tree.root).must_be_nil
    end
  end
  describe '#to_s' do
    it "must return '()'" do
      expect(tree.to_s).must_equal '()'
    end
  end
  describe '#inspect' do
    it 'must return correct inspection string' do
      expect(tree.inspect).must_equal "#{target_class}()"
    end
    it 'must return correct inspection string' do
      tree.root = tree.class.new_node(5)
      expect(tree.inspect).must_equal "#{target_class}(5)"
    end
  end
  describe '#height' do
    it 'must return 0 for empty tree' do
      expect(tree.height).must_equal 0
    end
    it 'must return 1 for single node tree' do
      tree.root = tree.class.new_node(5)
      expect(tree.height).must_equal 1
    end
    it 'must return correct height for multi-node tree' do
      tree.root = tree.class.new_node(:+)
      tree.root.left = tree.class.new_node(10)
      tree.root.right = tree.class.new_node(:*)
      tree.root.right.left = tree.class.new_node(20)
      tree.root.right.right = tree.class.new_node(30)
      expect(tree.height).must_equal 3
    end
  end
  describe '#search' do
    it 'must return true if value exists' do
      tree.root = target_class.new_node(5)
      expect(tree.search(5)).must_equal true
    end
    it 'must return false if value does not exist' do
      tree.root = target_class.new_node(5)
      expect(tree.search(10)).must_equal false
    end
    it 'must raise TypeError for invalid value' do
      tree.root = target_class.new_node(5)
      expect { tree.search(0.0 / 0.0) }.must_raise TypeError
    end
  end
  describe '#each' do
    it 'must iterate over tree nodes in order' do
      tree.root = tree.class.new_node(:+)
      tree.root.left = tree.class.new_node(10)
      tree.root.right = tree.class.new_node(:*)
      tree.root.right.left = tree.class.new_node(20)
      tree.root.right.right = tree.class.new_node(30)
      actual = []
      tree.each { |value| actual << "#{value} " }
      expect(actual.join).must_equal '10 + 20 * 30 '
    end
  end
end
Minitest.run if $PROGRAM_NAME == __FILE__
Minitest/Benchは、Minitestに含まれるベンチマークツールです。コードのパフォーマンス測定やプロファイリングを行う際に役立ちます。
Minitest/Benchを使うには、minitest/benchmarkを requireして、Minitest::Benchmarkを継承したクラスを定義します。
require 'minitest/benchmark'
class BenchmarkSuite < Minitest::Benchmark
  def bench_array_reverse
    assert_performance_linear 0.9999 do |n|
      n.times do
        (1..1000).to_a.reverse
      end
    end
  end
end
実行結果の例
Run options: --seed 42439
bench_array_reverse	 0.000143	 0.000332	 0.003698	 0.037538	 0.344312
.
Finished in 0.424787s, 2.3541 runs/s, 2.3541 assertions/s. 
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
上記の例では、bench_array_reverseメソッドを定義し、assert_performance_linearでベンチマークを行っています。このメソッドは、ブロック内のコードを繰り返し実行し、その実行時間がリニアなスケーリングであることを検証します。
Minitest/Benchには、以下のようなベンチマークメソッドが用意されています。
これらのメソッドを使うことで、コードのパフォーマンスを体系的に分析することができます。
Minitest/Benchmarkは、ベンチマーク結果の出力フォーマットをカスタマイズすることもできます。独自のフォーマッターを書いて、Minitest::Benchmark.formatterに設定します。
module MyFormatter
  def self.bench(result)
    # 独自の出力フォーマットを記述
    puts "My benchmark: #{result.path}: #{result.data}"
  end
end
Minitest::Benchmark.formatter = MyFormatter
また、GC回数の制御やRubyのインストールパスの変更などの細かい設定も可能です。
Minitest/Benchmarkは、Rubyコードのベンチマークとパフォーマンス分析を行うためのシンプルで強力なツールです。アサーションベースのベンチマークメソッドを使って、さまざまな入力サイズに対するコードの実行時間を確認し、最悪実行時間の次数を検証できます。フォーマッターをカスタマイズすることで、ベンチマーク結果の出力も自由にコントロールできます。Rubyコードの最適化を行う際に、Minitest/Benchmarkは非常に役立ちます。
Minitest/Mockは、Rubyプログラミング言語向けのモック（Mock）ライブラリです。モックは、テスト駆動開発（TDD）やユニットテストにおいて、依存するコンポーネントを置き換えてテストを行うための仕組みです。具体的には、テスト対象のコードが他のクラスやモジュールと相互作用している場合、その依存関係を模倣（モック化）して、テストをより制御可能にします。
Minitest/Mockは、Minitestフレームワークの一部として提供されており、Minitestを使用してRubyアプリケーションのユニットテストを行う際に、モックオブジェクトを容易に作成および利用できます。
モックを使用することで、テスト対象のコードが他のコードとの連携に問題がある場合でも、それを独立してテストすることが可能になります。また、外部のリソースや環境に依存する場合にも、モックを使ってその依存関係を排除してテストを行うことができます。これにより、より信頼性の高いテストスイートを構築することができます。
Minitest/Mockを使用すると、以下のようなことが可能です：
依存関係の置き換え： テスト対象のコードが他のクラスやモジュールと連携している場合、それらの依存関係をモックオブジェクトで置き換えることができます。これにより、テストの際に外部の状態に依存することなく、コードの特定の部分の振る舞いを確認できます。
テストの制御： モックオブジェクトを使用することで、テスト中に特定のメソッドが呼び出されたかどうかや、どのような引数で呼び出されたかなどを制御できます。これにより、特定の条件下での振る舞いをシミュレートしてテストを行うことができます。
テストの独立性： モックを使用することで、テスト対象のコードが他のコンポーネントに依存している場合でも、それらのコンポーネントの実際の実装を使用せずにテストを行うことができます。これにより、テストの独立性が高まり、テストスイート全体の信頼性が向上します。
総括すると、Minitest/Mockは、Rubyアプリケーションのユニットテストをより効果的に行うためのツールであり、依存関係の管理やテストの制御、テストの独立性の確保などに役立ちます。
Minitest/Mockを使ったユニットテストの例を示します。
まず、以下のようなクラスがあるとします。
userman.rb:
class UserManager
  def initialize(database)
    @database = database
  end
  def create_user(name, email)
    @database.insert(name, email)
  end
end
これをテストするために、データベースへの挿入をモック化してテストします。
 
userman_test.rb:
require 'minitest/spec'
require 'minitest/autorun'
require_relative 'userman'
describe UserManager do
  let(:mock_database) { Minitest::Mock.new }
  before do
    @user_manager = UserManager.new(mock_database)
  end
  describe "#create_user" do
    it "calls insert method on the database" do
      mock_database.expect(:insert, nil, ['John Doe', 'john@example.com'])
      @user_manager.create_user('John Doe', 'john@example.com')
      mock_database.verify
    end
  end
end
このテストでは、UserManagerクラスのcreate_userメソッドがデータベースにユーザーを挿入することを確認しています。しかし、実際のデータベース接続を行う代わりに、MiniTest::Mockを使用してinsertメソッドが呼ばれることをモック化しています。そして、verifyメソッドを呼び出して、モックが期待通りに振る舞っていることを確認しています。
このようにして、Minitest/Mockを使用して、外部の依存関係を置き換えてテストを行うことができます。
Minitestには、Stubとして使用できる機能が含まれています。Stubは、テスト中に特定のメソッド呼び出しに対する返り値や例外を設定するためのものです。MinitestのStub機能を使用することで、テスト中に依存するオブジェクトやメソッドの振る舞いを制御し、テストをより柔軟に行うことができます。
MinitestのStub機能を使って、特定のメソッド呼び出しに対する返り値を設定したり、メソッドが呼び出された際に例外を発生させたりすることができます。これにより、テスト中に実際のコードが依存する外部リソースやモジュールの振る舞いをシミュレートすることができます。
以下は、MinitestでStubを使用してメソッドの振る舞いを制御する例です：
class MyClass
  def self.my_method
    # 何か重要な処理
    return "Hello, world!"
  end
end
require 'minitest/autorun'
require 'minitest/spec'
describe MyClass do
  describe '#my_method' do
    it 'returns stubbed response' do
      MyClass.stub(:my_method, "Stubbed response") do
        expect(MyClass.my_method).must_equal "Stubbed response" 
      end
      # Stubの影響が終わった後には、通常の振る舞いを確認する
      expect(MyClass.my_method).must_equal "Hello, world!"
    end
  end
end
この例では、MyClassのmy_methodがテスト対象のメソッドです。テスト中にこのメソッドの振る舞いを制御するために、MyClass.stubを使用して特定の返り値を設定しています。その後、通常のメソッド呼び出しの振る舞いが正しいことを確認します。
これにより、テスト中に外部リソースやモジュールの振る舞いを制御し、テストの安定性や再現性を向上させることができます。