MinitestとRubyの変数アクセスバリデーション

minitestを試しに使っていたら、C#を触っていた時のオブジェクト内の変数(field)へのアクセス制限(プロパティ)やメソッドへのアクセス制限(public, private, protected)をRubyではどのように表現しているのか気になったのでメモを残しておく。

minitestの基本的な使い方。

minitestのテスト名はスネークケースで書かないとテストとして認められないらしい。(いつも通りキャメルケースで書いたら認識されなかった。)テストコードはこんな感じ。

require 'minitest/autorun'
require './User.rb' #テストするファイルを相対パスで指定。

require "minitest/reporters" #テスト結果をリッチに表示させるツール。gem install minitest-reportersが必要。
Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]

class UserTest < Minitest::Test #この継承関係にしておくことでテストとして機能させることができる。このクラスのインスタンス化は不要。

  def setup
    @hiroki = User.new('Hiroki', 70)
    @takumi = User.new('Takumi', 72)
  end

  def test_hiroki_heavier_than_takumi?
    assert_equal @hiroki.heavierThan?(@takumi), false
  end

  def test_takumi_heavier_than_hiroki?
    assert_equal @takumi.heavierThan?(@hiroki), true
  end
end

こんな感じでテストが書ける。この辺はNUnitとかと特に変わりない。

この時のテスト対象のコードが

class User
    attr_reader :name
    def initialize(name, weight)
      @name = name
      @weight = weight
    end
  
    def heavierThan?(otherUser)
      return otherUser.weight < @weight
    end
  
    protected
    def weight
      return @weight
    end
  end

こんな感じだった。
まず気になったのがattr_readerで、これがオブジェクトのフィールドに対するプロパティの役割をするらしい。C#では

public int Hp { get  => _hp; set { if (_hp > 0) { _hp = value; } } }

こんな風にgetとsetどちらが使用可能なのか、いちいち定義するが、Rubyでは

  • attr_readerなら読み込み可能
  • attr_writerなら書き込みが可能
  • attr_accessorなら読み書き可能

、となっている。実際、先ほどのUser classのnameプロパティを読み込み可能から読み書き可能に変更してみると、

require './User.rb'
@hiroki = User.new('Hiroki', 70)

puts @hiroki.name
@hiroki.name = "Haruya"
puts @hiroki.name

とすると、

Hiroki
Haruya

と出力することが出来る。ただ、この方法だと、C#でやってたみたいに、条件付きアクセサ(値が0以上なら書き込み可能にする)みたいなことはできなくなる

つぎに、User classのprotectedメソッド。C#ではそのクラスの子クラスのインスタンスからはアクセスできるフィールドになっていた。

ところが、User classに子クラスを作ってそこからアクセスしようとしたけど出来なかった。
いろいろ調べたところ、Rubyではprivateもprotectedもオブジェクト自身からしかアクセスできないメソッドだった。唯一の違いはPrivateでは関数形式での呼び出しができない、という点だった。

def weightPublic
      return @weight
    end

    protected
    def weightProtected
      return @weight
    end

    private
    def weightPrivate
      return @weight
    end
  

    def accessToItSelf

      puts "access to Public method: #{weightPublic}"#アクセス可能
      puts "access to Public method: #{self.weightPublic}"#アクセス可能

      puts "access to Protected method: #{weightProtected}"#アクセス可能
      puts "access to Protected method: #{self.weightProtected}"#アクセス可能

      puts "access to Private method: #{weightPrivate}"#アクセス可能
      puts "access to Private method: #{self.weightPrivate}"#アクセス不可能。
    end

C#ではオブジェクト自身のメソッドにアクセスする場合、selfを使っても、使わなくてもよかった。selfを使わずにアクセスしたいメソッド名だけでメソッドにアクセスすることを「関数形式の呼び出し」という。Rubyでは基本的にこれを許さないのだが、Public, Protectedなメソッドに対しては、これができるようになっている。
(3つあるうちの2つ許していたらあんまり関数形式の呼び出しを禁止していることになっていないと思うのだけど。。。)