【问题标题】:How to set private instance variable used within a method test?如何设置方法测试中使用的私有实例变量?
【发布时间】:2012-02-20 17:57:51
【问题描述】:

给定一个具有几个实例变量和一些方法的类。一些实例变量可以通过attr_readerattr_accessor 访问。因此,其他人是私人的。
一些私有实例变量在其中一个实例方法中设置,并在另一个方法中读取/使用。

为了测试,我正在使用 RSpec。由于我对 Ruby 还很陌生,并且想把所有事情都做好,所以我将我的测试定义为相当细粒度的。因此,每个实例方法都有一个describe 块,它们本身被划分为contexts 和its 的子集。一般环境前提条件用before定义。

但是,当测试其中一个方法时,它正在使用但未设置一个私有变量,我需要调用另一个方法,它正在设置这个变量。这对我来说似乎相当超重并且不是模块化的。

有没有办法将私有实例变量强制为某个值。类似于“提取”带有Object::instance_eval(:var) 的私有实例变量的值。

【问题讨论】:

    标签: ruby testing rspec access-specifier


    【解决方案1】:

    我刚刚通过创建一个孩子并添加一个访问器来解决它:

    class HasSecrets
      @muahaha
    end
    
    class TestMe < HasSecrets
      attr_accessor(:muahaha)
    end
    
    def test_stuff
      pwned = TestMe.new()
      pwned.muahaha = 'calc.exe'
    end
    

    【讨论】:

      【解决方案2】:

      正如您在问题中回答的那样,设置实例变量的最简单方法是使用 instance_eval 方法:

      obj.instance_eval('@a = 1')
      

      另一种方法是使用instance_variable_set:

      obj.instance_variable_set(:@a, 1)
      

      但我不建议在您的规范中这样做。规范都是关于测试对象的行为,通过使用 instance_eval 打破类封装来测试行为将使您的测试更加脆弱并且依赖于实现。

      对象状态隔离的另一种方法是存根访问器方法:

      class UnderTest
        attr_accessor :a
      
        def test_this
          do_something if a == 1
        end
      end
      
      #in your test
      under_test = UnderTest.new
      under_test.stub(:a).and_return(1)
      

      【讨论】:

      • 还没有考虑存根访问器。可能会使我的很多测试数据(一些文本文件,应该由我的程序检测和解析)过时。似乎是一个不错的方法。谢谢:)
      • 只是为了澄清,在您的回答中,您是声称 instance_eval 破坏了类封装还是 instance_variable_set 也这样做了?
      • @Seanny123 instance_evalinstance_variable_set 都修改实例私有的内部状态,不应在对象外部修改。
      • 仅仅为了测试目的而创建一个访问器是不是也很糟糕,因为它给下一个阅读你的代码的人留下了对象的状态将被正常情况下的外部对象修改的印象程序运行?
      • @Seanny123 通常当您发现自己想要存根私有变量时,这表明您需要某种依赖注入(无论是attr_accessor、构造函数参数还是其他),因此这种状态是接口的一部分(意味着您只想通过“mutator”方法保护其修改)。有时它也可能是设计中的一种气味(但这是一个广泛的话题,有点超出了这个问题的范围)。
      【解决方案3】:

      使用instance_variable_set:

      class SomeClass
        attr_reader :hello
        def initialize
          @hello = 5
        end
        # ...
      end
      
      a = SomeClass.new
      a.hello    # => 5
      
      a.instance_variable_set("@hello", 7)
      a.hello    # => 7
      

      【讨论】:

      • 谢谢。我知道这很容易。 :)
      猜你喜欢
      • 1970-01-01
      • 2014-10-16
      • 1970-01-01
      • 2017-07-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多