【问题标题】:Rspec, Rails: how to test private methods of controllers?Rspec,Rails:如何测试控制器的私有方法?
【发布时间】:2011-05-15 09:00:57
【问题描述】:

我有控制器:

class AccountController < ApplicationController
  def index
  end

  private
  def current_account
    @current_account ||= current_user.account
  end
end

如何使用 rspec 测试私有方法current_account

附:我使用 Rspec2 和 Ruby on Rails 3

【问题讨论】:

  • 这不能回答你的问题,但私有方法不应该被测试。你的测试应该只关心真实的东西——你的公共API。如果您的公共方法有效,那么他们调用的私有方法也有效。
  • 我不同意。在你的代码中测试任何足够复杂的特性都是有价值的。
  • 我也不同意。如果您的公共 API 有效,您只能假设您的私有方法按预期工作。但是您的规格可能是巧合。
  • 如果私有方法需要测试,最好将私有方法提取到一个可测试的新类中。
  • @RonLugge 你是对的。有了更多的后见之明和经验,我不同意我三年前的评论。 :)

标签: ruby-on-rails rspec


【解决方案1】:

使用#instance_eval

@controller = AccountController.new
@controller.instance_eval{ current_account }   # invoke the private method
@controller.instance_eval{ @current_account }.should eql ... # check the value of the instance variable

【讨论】:

  • 如果你愿意,也可以说:@controller.send(:current_account)。
  • Ruby 允许您使用 send 调用私有方法,但这并不一定意味着您应该这样做。测试私有方法是通过测试这些方法的公共接口来完成的。这种方法可行,但并不理想。如果该方法位于包含在控制器中的模块中会更好。然后它也可以独立于控制器进行测试。
  • 尽管这个答案在技术上回答了这个问题,但我不赞成,因为它违反了测试中的最佳实践。私有方法不应该被测试,仅仅因为 Ruby 让您能够规避方法可见性,并不意味着您应该滥用它。
  • Srdjan Pejic 能否详细说明为什么不应测试私有方法?
  • 我认为您对错误或不回答问题的答案投了反对票。这个答案是正确的,不应该被否决。如果您不同意测试私有方法的做法,请将其放在 cmets 中,因为它是很好的信息(正如许多人所做的那样),然后人们可以对该评论进行投票,这仍然表明了您的观点,而不会不必要地否决一个完全有效的答案。
【解决方案2】:

我使用发送方法。例如:

event.send(:private_method).should == 2

因为“发送”可以调用私有方法

【讨论】:

  • 如何使用.send在私有方法中测试实例变量?
【解决方案3】:

current_account 方法在哪里使用?它有什么用途?

通常,您不会测试私有方法,而是测试调用私有方法的方法。

【讨论】:

  • 理想情况下应该测试每一种方法。我在 rspec 中使用了 subject.send 和 subject.instance_eval,并取得了很大的成功
  • @Pullets 我不同意,正如我最初的回答所说,您应该测试 API 的公共方法,这些方法将调用私有方法。您应该测试您提供的 API,而不是只有您可以看到的私有方法。
  • 我同意@Ryan Bigg 的观点。您不测试私有方法。这消除了您重构或更改所述方法的实现的能力,即使该更改不会影响代码的公共部分。请在编写自动化测试时阅读最佳实践。
  • 嗯,也许我错过了什么。我编写的类有比公共方法更多的私有方法。仅通过公共 API 进行测试会产生数百个测试列表,这些测试不反映他们正在测试的代码。
  • 据我了解,如果你想在单元测试中实现真正的粒度,也应该测试私有方法。如果要重构代码,单元测试也必须相应地重构。这可以确保您的新代码也能按预期工作。
【解决方案4】:

您应该直接测试您的私有方法,它们可以而且应该通过执行来自公共方法的代码来间接测试。

这使您可以在不更改测试的情况下更改代码的内部结构。

【讨论】:

    【解决方案5】:

    您可以将私有或受保护的方法设为公开:

    MyClass.send(:public, *MyClass.protected_instance_methods) 
    MyClass.send(:public, *MyClass.private_instance_methods)
    

    只需将此代码放在您的测试类中替换您的类名即可。包括命名空间(如果适用)。

    【讨论】:

      【解决方案6】:
      require 'spec_helper'
      
      describe AdminsController do 
        it "-current_account should return correct value" do
          class AccountController
            def test_current_account
              current_account           
            end
          end
      
          account_constroller = AccountController.new
          account_controller.test_current_account.should be_correct             
      
         end
      end
      

      【讨论】:

        【解决方案7】:

        单元测试私有方法似乎与应用程序的行为脱节。

        您是先编写调用代码吗? 您的示例中未调用此代码。

        行为是:你想从另一个对象加载一个对象。

        context "When I am logged in"
          let(:user) { create(:user) }
          before { login_as user }
        
          context "with an account"
            let(:account) { create(:account) }
            before { user.update_attribute :account_id, account.id }
        
            context "viewing the list of accounts" do
              before { get :index }
        
              it "should load the current users account" do
                assigns(:current_account).should == account
              end
            end
          end
        end
        

        你为什么要从你的行为中脱离上下文来编写测试 应该试着描述一下?

        此代码是否在很多地方都被使用? 需要更通用的方法吗?

        https://www.relishapp.com/rspec/rspec-rails/v/2-8/docs/controller-specs/anonymous-controller

        【讨论】:

          【解决方案8】:

          使用rspec-context-private gem 在上下文中临时公开私有方法。

          gem 'rspec-context-private'
          

          它通过向您的项目添加共享上下文来工作。

          RSpec.shared_context 'private', private: true do
          
            before :all do
              described_class.class_eval do
                @original_private_instance_methods = private_instance_methods
                public *@original_private_instance_methods
              end
            end
          
            after :all do
              described_class.class_eval do
                private *@original_private_instance_methods
              end
            end
          
          end
          

          然后,如果您将 :private 作为元数据传递给 describe 块,则私有方法将在该上下文中公开。

          describe AccountController, :private do
            it 'can test private methods' do
              expect{subject.current_account}.not_to raise_error
            end
          end
          

          【讨论】:

            【解决方案9】:

            我知道这有点 hacky,但如果您希望方法可通过 rspec 测试但在 prod 中不可见,则它可以工作。

            class Foo
              def public_method
                #some stuff
              end
            
              eval('private') unless Rails.env == 'test'
            
              def testable_private_method
                # You can test me if you set RAILS_ENV=test
              end 
            end
            

            现在,当您可以运行时,您的规格如下:

            RAILS_ENV=test bundle exec rspec spec/foo_spec.rb 
            

            【讨论】:

              【解决方案10】:

              如果您需要测试私有函数,请创建一个调用私有函数的公共方法。

              【讨论】:

              • 我假设你的意思是这应该在你的单元测试代码中完成。这本质上就是 .instance_eval 和 .send 在一行代码中所做的事情。 (当较短的测试具有相同的效果时,谁愿意编写较长的测试?)
              • 叹息,这是一个 Rails 控制器。该方法必须是私有的。感谢您阅读实际问题。
              • 您始终可以将私有方法抽象为服务对象中的公共方法并以这种方式引用它。这样你就可以只测试公共方法,但仍然保持你的代码干燥。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-08-03
              • 1970-01-01
              相关资源
              最近更新 更多