Ruby'de korumalı ve özel yöntemleri test etmenin en iyi yolu nedir?


136

Standart Ruby Test::Unitçerçevesini kullanarak Ruby'de korumalı ve özel yöntemleri test etmenin en iyi yolu nedir ?

Eminim birileri "kamu testlerini sadece birim test etmelisiniz; birim testine ihtiyaç duyuyorsa, korumalı veya özel bir yöntem olmamalı" dır ve dogmatik bir şekilde iddia edecektir, ama bunu tartışmakla gerçekten ilgilenmiyorum. Ben çeşitli yöntemler var olan korunan veya iyi ve geçerli nedenlerle özel, bu nedenle ben test etmek için bir yol gerekir, bu özel / korumalı yöntemler orta karmaşıktır ve sınıfta kamu yöntemler düzgün bu korunan / özel yöntemlerle bağlıdır korunan / özel yöntemler.

Bir şey daha var ... Genellikle belirli bir sınıf için tüm yöntemleri bir dosyaya koyarım ve o sınıf için birim testleri başka bir dosyaya koyarım. İdeal olarak, ana kaynak dosyasını olabildiğince basit ve anlaşılır tutmak için bu "korumalı ve özel yöntemlerin birim testi" işlevselliğini, ana kaynak dosyasına değil, birim test dosyasına uygulamak istiyorum.


Yanıtlar:


135

Enkapsülasyonu send yöntemiyle atlayabilirsiniz:

myobject.send(:method_name, args)

Bu Ruby'nin bir özelliği. :)

Ruby 1.9 gelişimi sırasında sendgizliliğe saygı duymayı ve send!onu görmezden gelmeyi düşünen iç tartışma vardı, ama sonunda Ruby 1.9'da hiçbir şey değişmedi. Bir send!şeyleri tartışmak ve kırmak için aşağıdaki yorumları görmezden gelin.


Ben bu kullanım 1.9
Gene T

6
Çok sayıda yakut projeyi anında kıracaklarından, onu iptal edeceklerinden şüpheliyim
Orion Edwards

1
yakut 1.9 gelmez hemen hemen her şeyi bölünürler.
jes5199

1
Sadece not etmek: Hiçbir send!şeyi umursamıyorum, uzun zaman önce iptal edildi send/__send__, tüm görünürlük yöntemlerini çağırabilir - redmine.ruby-lang.org/repositories/revision/1?rev=13824
dolzenko 18:10

2
Orada public_send(dokümantasyon burada sen gizliliğine saygı istiyorsanız). Sanırım bu Ruby 1.9 için yeni.
Andrew Grimm

71

RSpec'i kullanmanın kolay bir yolu:

before(:each) do
  MyClass.send(:public, *MyClass.protected_instance_methods)  
end

9
Evet bu harika. Özel yöntemler için, korunmuş_instance_methods yerine ... private_instance_methods kullanın
Mike Blyth

12
Önemli uyarı: Bu, test paketi yürütmenizin geri kalanı için beklenmedik yan etkilere sahip olabilecek bu sınıftaki yöntemleri herkese açık hale getirir! Metotları ileride (: each) bir blokta tekrar korunan olarak yeniden tanımlamak veya gelecekte ürkütücü test hataları yaşamak isteyebilirsiniz.
Patojen

Bu aynı zamanda korkunç ve parlak
Robert

Bunu daha önce hiç görmedim ve fevkalade çalıştığını kanıtlayabilirim. Evet, hem korkunç hem de parlak ama test ettiğiniz yöntem düzeyinde kapsadığınız sürece, Pathogen'in ima ettiği beklenmedik yan etkilere sahip olmayacağınızı iddia ediyorum.
fuzzygroup

32

Sınama dosyanızdaki sınıfı yeniden açın ve yöntemi veya yöntemleri herkese açık olarak yeniden tanımlayın. Yöntemin bağırsaklarını yeniden tanımlamak zorunda değilsiniz, sadece sembolü publicçağrıya geçirin.

Orijinal sınıfınız şöyle tanımlanırsa:

class MyClass

  private

  def foo
    true
  end
end

Test dosyasında, sadece böyle bir şey yapın:

class MyClass
  public :foo

end

Daha publicfazla özel yöntem göstermek istiyorsanız, birden çok sembol iletebilirsiniz .

public :foo, :bar

2
Bu benim tercih edilen yaklaşımdır, çünkü kodunuza dokunulmaz ve sadece belirli bir testin gizliliğini ayarlar. Testleriniz bittikten sonra işleri eski haline getirmeyi unutmayın, aksi takdirde daha sonraki testleri bozabilirsiniz.
ktec

10

instance_eval() yardımcı olabilir:

--------------------------------------------------- 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

Özel yöntemlere ve örnek değişkenlere doğrudan erişmek için kullanabilirsiniz.

Ayrıca kullanmayı düşünebilirsiniz send() , bu da size özel ve korunan yöntemlere erişmenizi sağlayacaktır (James Baker'ın önerdiği gibi)

Alternatif olarak, özel / korumalı yöntemleri yalnızca bu nesne için herkese açık hale getirmek için test nesnenizin metasınıfını değiştirebilirsiniz.

    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

Bu, söz konusu sınıfın diğer nesnelerini etkilemeden bu yöntemleri çağırmanıza izin verecektir. Test dizininizdeki sınıfı yeniden açabilir ve test kodunuzdaki tüm örnekler için genel hale getirebilirsiniz, ancak bu genel arabirim testinizi etkileyebilir.


9

Geçmişte yaptığım bir yol:

class foo
  def public_method
    private_method
  end

private unless 'test' == Rails.env

  def private_method
    'private'
  end
end

8

Eminim birileri "kamu testlerini sadece birim test etmelisiniz; birim testine ihtiyaç duyuyorsa, korumalı veya özel bir yöntem olmamalı" dır ve dogmatik olarak iddia eder, ancak bunu tartışmakla gerçekten ilgilenmiyorum.

Bunları, bu yöntemlerin herkese açık olduğu yeni bir nesneye yeniden aktarabilir ve orijinal sınıfta kendilerine özel olarak devredebilirsiniz. Bu, teknik özellikleri sihirli metarubi olmadan test etmenizi sağlarken, yine de özel tutar.

İyi ve geçerli nedenlerle korunan veya özel birkaç yöntemim var

Bu geçerli nedenler nelerdir? Diğer OOP dilleri özel yöntemler olmadan tamamen kurtulabilir (smalltalk akla gelir - özel yöntemlerin yalnızca bir kural olarak var olduğu yerlerde).


Evet, ancak Smalltalker'ların çoğu bunun dilin iyi bir özelliği olduğunu düşünmüyordu.
aenw

6

@ WillSargent'ın yanıtına benzer şekilde, FactoryGirl describeile bunları oluşturma / güncelleme işlemine gerek kalmadan bazı korumalı doğrulayıcıları test etmek için özel bir durumda test etmek için bir blokta kullandığım şey (ve private_instance_methodsbenzer şekilde kullanabilirsiniz ):

  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

5

Açıklanan sınıf için tüm korumalı ve özel yöntemi herkese açık hale getirmek için spec_helper.rb dosyasına aşağıdakileri ekleyebilir ve spec dosyalarınızın herhangi birine dokunmanıza gerek kalmaz.

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

3

Sınıfı "yeniden açabilir" ve özel olana yetki veren yeni bir yöntem sağlayabilirsiniz:

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

2

Muhtemelen instance_eval () kullanmaya eğilimliydim. Ancak, instance_eval () hakkında bilgi sahibi olmadan önce birim test dosyamda türetilmiş bir sınıf oluştururdum. Sonra özel yöntem (ler) genel olarak ayarlamak.

Aşağıdaki örnekte, build_year_range yöntemi PublicationSearch :: ISIQuery sınıfında gizlidir. Sadece test amacıyla yeni bir sınıf türetmek, herkese açık ve dolayısıyla doğrudan test edilebilecek bir yöntem (ler) belirlememe olanak tanır. Benzer şekilde, türetilmiş sınıf, daha önce gösterilmeyen 'sonuç' adlı bir örnek değişkeni ortaya koyar.

# A derived class useful for testing.
class MockISIQuery < PublicationSearch::ISIQuery
    attr_accessor :result
    public :build_year_range
end

Birim testimde MockISIQuery sınıfını başlatan ve doğrudan build_year_range () yöntemini test eden bir test durumum var.


2

Testte :: Birim çerçevesi yazabilir,

MyClass.send(:public, :method_name)

Burada "method_name" özel yöntemdir.

& bu yöntemi çağırırken yazabilir,

assert_equal expected, MyClass.instance.method_name(params)

1

İşte kullandığım Class'a genel bir ekleme. Sadece test ettiğiniz yöntemi herkese açık hale getirmekten biraz daha av tüfeği, ancak çoğu durumda önemli değil ve çok daha okunabilir.

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

Erişim korumalı / özel yöntemlere gönderme kullanarak edilir , böylece 1.9 kırık önerilen bir çözüm değildir.


1

Yukarıdaki en iyi yanıtı düzeltmek için: Ruby 1.9.1'de, tüm iletileri gönderen Object # send ve gizliliğe saygı gösteren Object # public_send'dir.


1
Bu cevaba yorum eklemelisiniz, başka bir cevabı düzeltmek için yeni bir cevap yazmamalısınız.
zishe

1

Obj.send yerine tekil bir yöntem kullanabilirsiniz. Test sınıfınızda 3 satır daha kod vardır ve test edilecek gerçek kodda değişiklik yapılmasını gerektirmez.

def obj.my_private_method_publicly (*args)
  my_private_method(*args)
end

Test durumlarında daha sonra my_private_method_publiclytest etmek istediğinizde kullanabilirsiniz my_private_method.

http://mathandprogramming.blogspot.com/2010/01/ruby-testing-private-methods.html

obj.sendözel yöntemler için send!1.9 ile değiştirildi , ancak daha sonra send!tekrar kaldırıldı. Bu yüzden obj.sendmükemmel çalışıyor.


1

Partiye geç kaldığımı biliyorum, ama özel yöntemleri test etme .... Bunu yapmak için bir neden düşünemiyorum. Herkese açık olarak erişilebilir bir yöntem, bu özel yöntemi bir yerde kullanmaktır, genel yöntemi ve bu özel yöntemin kullanılmasına neden olacak çeşitli senaryoları test edin. Bir şey giriyor, bir şey çıkıyor. Özel yöntemleri test etmek büyük bir hayırdır ve kodunuzu daha sonra yeniden düzenlemeyi çok daha zorlaştırır. Bir sebepten dolayı özeldirler.


14
Hala bu pozisyonu anlamıyorum: Evet, özel yöntemler bir sebepten dolayı özeldir, fakat hayır, bu nedenin testle ilgisi yoktur.
Sebastian vom Meer

Keşke bunu daha fazla oylayabilseydim. Bu konudaki tek doğru cevap.
Psynix

Bu bakış açısına sahipseniz, neden birim testlerle uğraşasınız ki? Özellik özellikleri yazmanız yeterlidir: giriş girer, sayfa çıkar, aradaki her şey doğru şekilde kapatılmalıdır
ohhh

1

Bunu yapmak için:

disrespect_privacy @object do |p|
  assert p.private_method
end

Bunu test_helper dosyanıza uygulayabilirsiniz:

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

Heh bu bazı eğlendik: gist.github.com/amomchilov/ef1c84325fe6bb4ce01e0f0780837a82 yeniden adlandırıldı Disrespectiçin PrivacyViolatorve yapılan: (P) disrespect_privacyama sadece süresince, sarıcı nesneye hedef nesneyi hatırlatmak böylece, yöntem geçici blok en bağlayıcı düzenleme blok. Bu şekilde bir blok parametresi kullanmanıza gerek kalmaz, sadece aynı ada sahip nesneyi referans almaya devam edebilirsiniz.
Alexander - Monica
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.