Rspec, Rails: จะทดสอบวิธีส่วนตัวของคอนโทรลเลอร์ได้อย่างไร?


125

ฉันมีตัวควบคุม:

class AccountController < ApplicationController
  def index
  end

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

จะทดสอบ private method current_accountด้วย rspec ได้อย่างไร?

ปล. ฉันใช้ Rspec2 และ Ruby บน Rails 3


8
สิ่งนี้ไม่ได้ตอบคำถามคุณ แต่ไม่ควรทดสอบวิธีการส่วนตัว การทดสอบของคุณควรสนใจเฉพาะของจริงนั่นคือ API สาธารณะของคุณ หากวิธีการสาธารณะของคุณใช้ได้ผลวิธีการส่วนตัวที่พวกเขาเรียกว่าก็ใช้ได้เช่นกัน
Samy Dindane

77
ฉันไม่เห็นด้วย. มีคุณค่าในการทดสอบคุณลักษณะที่ซับซ้อนเพียงพอในโค้ดของคุณ
whitehat101

11
ฉันไม่เห็นด้วย หาก API สาธารณะของคุณใช้งานได้คุณสามารถสมมติว่าเมธอดส่วนตัวของคุณทำงานได้ตามที่คาดไว้เท่านั้น แต่รายละเอียดของคุณอาจจะผ่านไปโดยบังเอิญ
Rimian

4
มันจะดีกว่าถ้าแยกเมธอดส่วนตัวไปยังคลาสใหม่ซึ่งสามารถทดสอบได้หากเมธอดส่วนตัวต้องการการทดสอบ
Kris

10
@RonLugge คุณพูดถูก ด้วยการมองย้อนกลับและประสบการณ์ที่มากขึ้นฉันไม่เห็นด้วยกับความคิดเห็นที่อายุสามขวบของฉัน :)
Samy Dindane

คำตอบ:


196

ใช้ #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

94
หากต้องการคุณสามารถพูดว่า: @ controller.send (: current_account)
ความสับสน

13
Ruby ช่วยให้คุณสามารถเรียกใช้วิธีการส่วนตัวด้วยการส่ง แต่นั่นไม่ได้หมายความว่าคุณควร การทดสอบวิธีการส่วนตัวทำได้โดยการทดสอบอินเทอร์เฟซสาธารณะกับวิธีการเหล่านั้น วิธีนี้ใช้ได้ผล แต่ไม่เหมาะ จะดีกว่าถ้าเมธอดอยู่ในโมดูลที่รวมอยู่ในคอนโทรลเลอร์ จากนั้นก็สามารถทดสอบโดยไม่ขึ้นกับคอนโทรลเลอร์ได้เช่นกัน
Brian Hogan

9
แม้ว่าคำตอบนี้จะตอบคำถามในทางเทคนิค แต่ฉันก็ไม่ลงคะแนนเนื่องจากละเมิดแนวทางปฏิบัติที่ดีที่สุดในการทดสอบ ไม่ควรทดสอบวิธีการส่วนตัวและเนื่องจาก Ruby ให้ความสามารถในการหลีกเลี่ยงการมองเห็นวิธีการนั้นไม่ได้หมายความว่าคุณควรละเมิด
Srdjan Pejic

24
Srdjan Pejic คุณสามารถอธิบายได้อย่างละเอียดว่าเหตุใดจึงไม่ควรทดสอบวิธีการส่วนตัว
John Bachir

35
ฉันคิดว่าคุณลงคะแนนคำตอบที่ผิดหรือไม่ตอบคำถาม คำตอบนี้ถูกต้องและไม่ควรลงคะแนน หากคุณไม่เห็นด้วยกับการทดสอบวิธีการส่วนตัวให้ใส่ความคิดเห็นนั้นไว้ในความคิดเห็นเนื่องจากเป็นข้อมูลที่ดี (เช่นเดียวกับหลาย ๆ คน) จากนั้นผู้คนก็สามารถโหวตความคิดเห็นนั้นได้ซึ่งยังคงแสดงประเด็นของคุณโดยไม่ต้องลดคะแนนคำตอบที่ถูกต้องโดยไม่จำเป็น
traday

37

ฉันใช้วิธีส่ง เช่น:

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

เนื่องจาก "ส่ง" สามารถเรียกวิธีการส่วนตัว


คุณจะทดสอบตัวแปรอินสแตนซ์ภายในวิธีการส่วนตัวโดยใช้.sendอย่างไร
12

23

เมธอด current_account ถูกใช้ที่ไหน มีจุดประสงค์อะไร?

โดยทั่วไปคุณจะไม่ทดสอบวิธีการส่วนตัว แต่เป็นการทดสอบวิธีที่เรียกวิธีส่วนตัว


5
ตามหลักการแล้วควรทดสอบทุกวิธี ฉันใช้ทั้ง subject.send และ subject.instance_eval ประสบความสำเร็จอย่างมากใน rspec
David W. Keith

7
@Pullets ฉันไม่เห็นด้วยคุณควรทดสอบเมธอดสาธารณะของ API ซึ่งจะเรียกใช้งานส่วนตัวตามที่คำตอบเดิมของฉันบอก คุณควรทดสอบ API ที่คุณระบุไม่ใช่วิธีการส่วนตัวที่คุณเท่านั้นที่เห็น
Ryan Bigg

5
ฉันเห็นด้วยกับ @Ryan Bigg คุณไม่ได้ทดสอบวิธีการส่วนตัว วิธีนี้จะลบความสามารถของคุณในการ refactor หรือเปลี่ยนแปลงการใช้งานวิธีการดังกล่าวแม้ว่าการเปลี่ยนแปลงนั้นจะไม่ส่งผลกระทบต่อส่วนสาธารณะของโค้ดของคุณก็ตาม โปรดอ่านแนวทางปฏิบัติที่ดีที่สุดเมื่อเขียนแบบทดสอบอัตโนมัติ
Srdjan Pejic

4
อืมบางทีฉันอาจจะขาดอะไรไป ชั้นเรียนที่ฉันเขียนมีวิธีการส่วนตัวมากกว่าชั้นเรียนสาธารณะ การทดสอบผ่าน API สาธารณะเท่านั้นจะทำให้มีรายการการทดสอบหลายร้อยรายการที่ไม่สะท้อนโค้ดที่กำลังทดสอบ
David W. Keith

9
ตามความเข้าใจของฉันหากคุณต้องการบรรลุความละเอียดจริงในการทดสอบหน่วยวิธีการส่วนตัวควรทดสอบด้วย หากคุณต้องการ refactor รหัสหน่วยทดสอบจะต้อง refactor ตาม สิ่งนี้ทำให้มั่นใจได้ว่าโค้ดใหม่ของคุณจะทำงานได้ตามที่คาดไว้
Indika K

7

คุณไม่ควรทดสอบวิธีการส่วนตัวของคุณโดยตรงพวกเขาสามารถและควรทดสอบทางอ้อมโดยใช้รหัสจากวิธีการสาธารณะ

วิธีนี้ช่วยให้คุณสามารถเปลี่ยนรหัสภายในของคุณได้โดยไม่ต้องเปลี่ยนการทดสอบ


4

คุณสามารถกำหนดวิธีการส่วนตัวหรือป้องกันเป็นสาธารณะได้:

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

เพียงวางรหัสนี้ในชั้นทดสอบของคุณโดยแทนที่ชื่อชั้นเรียนของคุณ รวมเนมสเปซถ้ามี


3
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

1

วิธีการส่วนตัวในการทดสอบหน่วยดูเหมือนจะไม่สอดคล้องกับลักษณะการทำงานของแอปพลิเคชัน

คุณเขียนรหัสการโทรก่อนหรือไม่? รหัสนี้ไม่ได้ถูกเรียกในตัวอย่างของคุณ

ลักษณะการทำงานคือคุณต้องการให้วัตถุโหลดจากวัตถุอื่น

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


1

ใช้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

0

หากคุณต้องการทดสอบฟังก์ชันส่วนตัวให้สร้างวิธีการสาธารณะที่เรียกใช้ฟังก์ชันส่วนตัว


3
ฉันคิดว่าคุณหมายความว่าควรทำในรหัสทดสอบหน่วยของคุณ นั่นคือสิ่งที่ .instance_eval และ. ส่งทำในโค้ดบรรทัดเดียว (และใครอยากจะเขียนแบบทดสอบอีกต่อไปเมื่อข้อสอบสั้นลงก็มีผลเช่นเดียวกัน)
David W. Keith

3
ถอนหายใจมันเป็นตัวควบคุมราง วิธีนี้จะต้องมีความเป็นส่วนตัว ขอบคุณที่อ่านคำถามจริง
Michael Johnston

คุณสามารถสรุปเมธอดส่วนตัวเป็นเมธอดสาธารณะในเซอร์วิสออบเจ็กต์และอ้างอิงวิธีนั้นได้ ด้วยวิธีนี้คุณสามารถทดสอบได้เฉพาะวิธีการสาธารณะและยังคงทำให้รหัสของคุณแห้ง
Jason

0

ฉันรู้ว่านี่เป็นเรื่องที่แฮ็ค แต่ใช้ได้ถ้าคุณต้องการให้วิธีการทดสอบโดย 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 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.