Apa cara terbaik untuk menguji unit metode terproteksi dan privat di Ruby, menggunakan Test::Unit
kerangka kerja Ruby standar ?
Saya yakin seseorang akan menyalurkan dan secara dogmatis menegaskan bahwa "Anda hanya harus menguji metode publik; jika perlu pengujian unit, itu tidak boleh menjadi metode yang dilindungi atau pribadi", tetapi saya tidak terlalu tertarik untuk memperdebatkannya. Aku punya beberapa metode yang sedang dilindungi atau swasta untuk kebaikan dan alasan yang sah, / metode ini swasta dilindungi yang cukup kompleks, dan metode umum di kelas tergantung dilindungi / metode ini swasta berfungsi dengan benar, karena itu saya perlu cara untuk menguji metode dilindungi / pribadi.
Satu hal lagi ... Saya biasanya meletakkan semua metode untuk kelas tertentu dalam satu file, dan tes unit untuk kelas itu di file lain. Idealnya, saya ingin semua keajaiban untuk mengimplementasikan fungsi "uji unit metode dilindungi dan pribadi" ini ke dalam file pengujian unit, bukan file sumber utama, untuk menjaga file sumber utama sesederhana dan sesederhana mungkin.
sumber
Jawaban:
Anda dapat melewati enkapsulasi dengan metode kirim:
myobject.send(:method_name, args)
Ini adalah 'fitur' Ruby. :)
Ada perdebatan internal selama pengembangan Ruby 1.9 yang menganggap
send
menghargai privasi dansend!
mengabaikannya, tetapi pada akhirnya tidak ada yang berubah di Ruby 1.9. Abaikan komentar di bawah ini membahassend!
dan memecahkan sesuatu.sumber
send!
hal, itu dicabut lama,send/__send__
dapat memanggil metode dari semua visibilitas - redmine.ruby-lang.org/repositories/revision/1?rev=13824public_send
(dokumentasi di sini ) jika Anda ingin menghormati privasi. Saya pikir itu baru di Ruby 1.9.Berikut salah satu cara mudah jika Anda menggunakan RSpec:
before(:each) do MyClass.send(:public, *MyClass.protected_instance_methods) end
sumber
Buka kembali kelas di file pengujian Anda, dan tentukan ulang metode atau metode sebagai publik. Anda tidak perlu mendefinisikan ulang keberanian dari metode itu sendiri, cukup teruskan simbol ke dalam
public
panggilan.Jika Anda kelas asli didefinisikan seperti ini:
class MyClass private def foo true end end
Di file pengujian Anda, lakukan saja seperti ini:
class MyClass public :foo end
Anda dapat memberikan beberapa simbol
public
jika Anda ingin mengekspos metode yang lebih pribadi.public :foo, :bar
sumber
instance_eval()
mungkin membantu:--------------------------------------------------- Object#instance_eval obj.instance_eval(string [, filename [, lineno]] ) => obj obj.instance_eval {| | block } => obj ------------------------------------------------------------------------ Evaluates a string containing Ruby source code, or the given block, within the context of the receiver (obj). In order to set the context, the variable self is set to obj while the code is executing, giving the code access to obj's instance variables. In the version of instance_eval that takes a String, the optional second and third parameters supply a filename and starting line number that are used when reporting compilation errors. class Klass def initialize @secret = 99 end end k = Klass.new k.instance_eval { @secret } #=> 99
Anda dapat menggunakannya untuk mengakses metode privat dan variabel instance secara langsung.
Anda juga dapat mempertimbangkan untuk menggunakan
send()
, yang juga akan memberi Anda akses ke metode pribadi dan dilindungi (seperti yang disarankan James Baker)Alternatifnya, Anda bisa memodifikasi metaclass objek pengujian Anda untuk membuat metode privat / dilindungi publik hanya untuk objek itu.
test_obj.a_private_method(...) #=> raises NoMethodError test_obj.a_protected_method(...) #=> raises NoMethodError class << test_obj public :a_private_method, :a_protected_method end test_obj.a_private_method(...) # executes test_obj.a_protected_method(...) # executes other_test_obj = test.obj.class.new other_test_obj.a_private_method(...) #=> raises NoMethodError other_test_obj.a_protected_method(...) #=> raises NoMethodError
Ini akan memungkinkan Anda memanggil metode ini tanpa memengaruhi objek lain dari kelas itu. Anda dapat membuka kembali kelas dalam direktori pengujian Anda dan menjadikannya publik untuk semua instance dalam kode pengujian Anda, tetapi hal itu mungkin memengaruhi pengujian Anda terhadap antarmuka publik.
sumber
Salah satu cara saya melakukannya di masa lalu adalah:
class foo def public_method private_method end private unless 'test' == Rails.env def private_method 'private' end end
sumber
Anda juga dapat memfaktor ulang mereka menjadi objek baru di mana metode tersebut bersifat publik, dan mendelegasikannya secara pribadi di kelas asli. Ini akan memungkinkan Anda untuk menguji metode tanpa metaruby ajaib dalam spesifikasi Anda sambil tetap merahasiakannya.
Apa alasan yang valid itu? Bahasa OOP lain bisa lolos tanpa metode privat sama sekali (pembicaraan kecil muncul di benak - di mana metode privat hanya ada sebagai konvensi).
sumber
Mirip dengan tanggapan @ WillSargent, inilah yang telah saya gunakan dalam sebuah
describe
blok untuk kasus khusus menguji beberapa validator yang dilindungi tanpa perlu melalui proses kelas berat untuk membuat / memperbaruinya dengan FactoryGirl (dan Anda dapat menggunakannya denganprivate_instance_methods
cara yang sama):describe "protected custom `validates` methods" do # Test these methods directly to avoid needing FactoryGirl.create # to trigger before_create, etc. before(:all) do @protected_methods = MyClass.protected_instance_methods MyClass.send(:public, *@protected_methods) end after(:all) do MyClass.send(:protected, *@protected_methods) @protected_methods = nil end # ...do some tests... end
sumber
Agar publik semua metode dilindungi dan privat untuk kelas yang dijelaskan, Anda dapat menambahkan berikut ini ke spec_helper.rb Anda dan tidak perlu menyentuh file spesifikasi Anda.
RSpec.configure do |config| config.before(:each) do described_class.send(:public, *described_class.protected_instance_methods) described_class.send(:public, *described_class.private_instance_methods) end end
sumber
Anda dapat "membuka kembali" kelas dan memberikan metode baru yang mendelegasikan ke kelas pribadi:
class Foo private def bar; puts "Oi! how did you reach me??"; end end # and then class Foo def ah_hah; bar; end end # then Foo.new.ah_hah
sumber
Saya mungkin akan cenderung menggunakan instance_eval (). Sebelum saya tahu tentang instance_eval (), saya akan membuat kelas turunan di file pengujian unit saya. Saya kemudian akan mengatur metode privat menjadi publik.
Pada contoh di bawah ini, metode build_year_range bersifat privat di kelas PublicationSearch :: ISIQuery. Mendapatkan kelas baru hanya untuk tujuan pengujian memungkinkan saya untuk menyetel metode menjadi publik dan, oleh karena itu, dapat diuji secara langsung. Demikian juga, kelas turunan mengekspos variabel instan yang disebut 'hasil' yang sebelumnya tidak diekspos.
# A derived class useful for testing. class MockISIQuery < PublicationSearch::ISIQuery attr_accessor :result public :build_year_range end
Dalam pengujian unit saya, saya memiliki kasus uji yang membuat instance kelas MockISIQuery dan langsung menguji metode build_year_range ().
sumber
Dalam Test :: Kerangka unit dapat menulis,
MyClass.send(:public, :method_name)
Di sini "method_name" adalah metode pribadi.
& sambil memanggil metode ini dapat menulis,
sumber
Berikut adalah tambahan umum untuk Kelas yang saya gunakan. Ini sedikit lebih banyak daripada hanya mempublikasikan metode yang Anda uji, tetapi dalam banyak kasus itu tidak masalah, dan itu jauh lebih mudah dibaca.
class Class def publicize_methods saved_private_instance_methods = self.private_instance_methods self.class_eval { public *saved_private_instance_methods } begin yield ensure self.class_eval { private *saved_private_instance_methods } end end end MyClass.publicize_methods do assert_equal 10, MyClass.new.secret_private_method end
Menggunakan kirim ke akses yang dilindungi / metode swasta yang rusak di 1.9, jadi bukan solusi yang direkomendasikan.
sumber
Untuk mengoreksi jawaban teratas di atas: di Ruby 1.9.1, Object # sendlah yang mengirim semua pesan, dan Object # public_send yang menghormati privasi.
sumber
Dari pada obj.send Anda bisa menggunakan metode tunggal. Ini adalah 3 baris kode lagi di kelas pengujian Anda dan tidak memerlukan perubahan pada kode sebenarnya untuk diuji.
def obj.my_private_method_publicly (*args) my_private_method(*args) end
Dalam kasus pengujian, Anda kemudian menggunakan
my_private_method_publicly
kapan pun Anda ingin mengujimy_private_method
.http://mathandprogramming.blogspot.com/2010/01/ruby-testing-private-methods.html
obj.send
untuk metode privat digantikan olehsend!
1.9, tapi kemudiansend!
dihapus lagi. Jadiobj.send
bekerja dengan baik.sumber
Saya tahu saya terlambat ke pesta, tetapi jangan menguji metode pribadi .... Saya tidak dapat memikirkan alasan untuk melakukan ini. Metode yang dapat diakses publik menggunakan metode privat tersebut di suatu tempat, menguji metode publik dan berbagai skenario yang akan menyebabkan metode privat tersebut digunakan. Sesuatu masuk, sesuatu keluar. Menguji metode privat adalah hal yang tidak boleh dilakukan, dan akan semakin sulit untuk memfaktorkan ulang kode Anda nanti. Mereka bersifat pribadi karena suatu alasan.
sumber
Untuk melakukan ini:
disrespect_privacy @object do |p| assert p.private_method end
Anda dapat menerapkan ini di file test_helper Anda:
class ActiveSupport::TestCase def disrespect_privacy(object_or_class, &block) # access private methods in a block raise ArgumentError, 'Block must be specified' unless block_given? yield Disrespect.new(object_or_class) end class Disrespect def initialize(object_or_class) @object = object_or_class end def method_missing(method, *args) @object.send(method, *args) end end end
sumber
Disrespect
menjadiPrivacyViolator
(: P) dan membuatdisrespect_privacy
metode untuk sementara mengedit pengikatan blok, untuk mengingatkan objek target ke objek pembungkus, tetapi hanya untuk durasi dari blok. Dengan begitu Anda tidak perlu menggunakan parameter blok, Anda dapat terus mereferensikan objek dengan nama yang sama.