webentwicklung-frage-antwort-db.com.de

Was ist der beste Weg, um geschützte und private Methoden in Ruby zu testen?

Was ist der beste Weg, um geschützte und private Methoden in Ruby zu testen, mit dem Standard Ruby Test::Unit Framework?

Ich bin mir sicher, dass sich jemand meldet und dogmatisch behauptet, "Sie sollten nur öffentliche Methoden testen. Wenn Unit-Tests erforderlich sind, sollte es sich nicht um eine geschützte oder private Methode handeln", aber ich bin nicht wirklich daran interessiert, darüber zu debattieren. Ich habe mehrere Methoden, die sind geschützt sind oder aus guten und gültigen Gründen privat sind. Diese privaten/geschützten Methoden sind mäßig komplex und die öffentlichen Methoden in der Klasse hängen davon ab, dass diese geschützten/privaten Methoden ordnungsgemäß funktionieren. Deshalb brauche ich eine Möglichkeit, die geschützten/privaten Methoden zu testen.

Eine weitere Sache ... Im Allgemeinen füge ich alle Methoden für eine bestimmte Klasse in eine Datei ein und die Unit-Tests für diese Klasse in eine andere Datei. Im Idealfall möchte ich, dass all die Magie diese "Komponententest geschützter und privater Methoden" -Funktionalität in die Komponententestdatei und nicht in die Hauptquelldatei implementiert, um die Hauptquelldatei so einfach und unkompliziert wie möglich zu halten.

131
Brent Chapman

Sie können die Kapselung mit der send-Methode umgehen:

myobject.send(:method_name, args)

Dies ist ein "Feature" von Ruby. :)

Während der Ruby 1.9-Entwicklung gab es eine interne Debatte, in der erwogen wurde, send die Privatsphäre zu respektieren und send! Zu ignorieren, aber am Ende änderte sich nichts an Ruby 1.9 Ignorieren Sie die folgenden Kommentare, in denen send! Besprochen wird und die Dinge aufgeschlüsselt werden.

133
James Baker

Hier ist eine einfache Möglichkeit, wenn Sie RSpec verwenden:

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

Öffnen Sie einfach die Klasse in Ihrer Testdatei erneut und definieren Sie die Methode (n) als öffentlich. Sie müssen die Eingeweide der Methode selbst nicht neu definieren, sondern übergeben das Symbol einfach an den Aufruf public.

Wenn Ihre ursprüngliche Klasse folgendermaßen definiert ist:

class MyClass

  private

  def foo
    true
  end
end

Führen Sie in Ihrer Testdatei einfach Folgendes aus:

class MyClass
  public :foo

end

Sie können mehrere Symbole an public übergeben, wenn Sie mehr private Methoden verfügbar machen möchten.

public :foo, :bar
31
Aaron Hinni

instance_eval() könnte helfen:

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

Sie können damit direkt auf private Methoden und Instanzvariablen zugreifen.

Sie können auch die Verwendung von send() in Betracht ziehen, wodurch Sie auch Zugriff auf private und geschützte Methoden erhalten (wie von James Baker vorgeschlagen).

Alternativ können Sie die Metaklasse Ihres Testobjekts ändern, um die privaten/geschützten Methoden nur für dieses Objekt öffentlich zu machen.

    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

Auf diese Weise können Sie diese Methoden aufrufen, ohne andere Objekte dieser Klasse zu beeinflussen. Sie können die Klasse in Ihrem Testverzeichnis erneut öffnen und sie für alle Instanzen in Ihrem Testcode öffentlich machen. Dies kann sich jedoch auf Ihren Test der öffentlichen Schnittstelle auswirken.

10
rampion

Eine Möglichkeit, wie ich es in der Vergangenheit getan habe, ist:

class foo
  def public_method
    private_method
  end

private unless 'test' == Rails.env

  def private_method
    'private'
  end
end
9
Scott

Ich bin mir sicher, dass sich jemand meldet und dogmatisch behauptet, "Sie sollten nur öffentliche Methoden testen. Wenn Unit-Tests erforderlich sind, sollte es sich nicht um eine geschützte oder private Methode handeln", aber ich bin nicht wirklich daran interessiert, darüber zu debattieren.

Sie können diese auch in ein neues Objekt umgestalten, in dem diese Methoden öffentlich sind, und sie privat in der ursprünglichen Klasse delegieren. Auf diese Weise können Sie die Methoden ohne Magic Metaruby in Ihren Spezifikationen testen, ohne sie jedoch zu gefährden.

Ich habe verschiedene Methoden, die aus guten und gültigen Gründen geschützt oder privat sind

Was sind diese gültigen Gründe? Andere OOP Sprachen können überhaupt ohne private Methoden davonkommen (Smalltalk kommt in den Sinn - wo private Methoden nur als Konvention existieren).

7
user52804

Um alle geschützten und privaten Methoden für die beschriebene Klasse öffentlich zu machen, können Sie Ihrer spec_helper.rb Folgendes hinzufügen und müssen keine Ihrer Spezifikationsdateien berühren.

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
5
Sean Tan

Ähnlich wie bei der Antwort von @ WillSargent habe ich in einem describe -Block Folgendes für den speziellen Fall des Testens einiger geschützter Validatoren verwendet, ohne dass Sie den Prozess des Erstellens/Aktualisierens mit FactoryGirl durchlaufen müssen (und Sie könnten ihn verwenden) private_instance_methods ähnlich):

  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
qix

Ich weiß, dass ich zu spät zur Party komme, aber teste keine privaten Methoden ... Ich kann mir keinen Grund vorstellen, dies zu tun. Eine öffentlich zugängliche Methode verwendet diese private Methode irgendwo. Testen Sie die öffentliche Methode und die verschiedenen Szenarien, in denen diese private Methode verwendet wird. Da geht was rein, da kommt was raus. Das Testen privater Methoden ist ein großes Nein, und es ist viel schwieriger, den Code später umzugestalten. Sie sind aus einem bestimmten Grund privat.

3
Binary Logic

Sie können die Klasse "wieder öffnen" und eine neue Methode bereitstellen, die an die private delegiert:

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

In Test :: Unit Framework kann schreiben,

MyClass.send(:public, :method_name)

Hier ist "method_name" eine private Methode.

& beim Aufruf dieser Methode kann schreiben,

assert_equal expected, MyClass.instance.method_name(params)
2
rahul patil

Ich würde wahrscheinlich dazu tendieren, instance_eval () zu verwenden. Bevor ich jedoch über instance_eval () Bescheid wusste, habe ich in meiner Unit-Test-Datei eine abgeleitete Klasse erstellt. Ich würde dann die private (n) Methode (n) als öffentlich festlegen.

Im folgenden Beispiel ist die build_year_range-Methode in der PublicationSearch :: ISIQuery-Klasse privat. Das Ableiten einer neuen Klasse nur zu Testzwecken ermöglicht es mir, eine oder mehrere Methoden als öffentlich und daher direkt testbar festzulegen. Ebenso macht die abgeleitete Klasse eine Instanzvariable namens 'result' verfügbar, die zuvor nicht verfügbar gemacht wurde.

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

In meinem Komponententest habe ich einen Testfall, der die MockISIQuery-Klasse instanziiert und die build_year_range () -Methode direkt testet.

2
Mike

Hier ist eine allgemeine Ergänzung zu Class, die ich benutze. Es ist ein bisschen mehr Schrotflinte als nur die Methode, die Sie testen, öffentlich zu machen, aber in den meisten Fällen spielt es keine Rolle und es ist viel besser lesbar.

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

Die Verwendung von send, um auf geschützte/private Methoden zuzugreifen is ist in 1.9 defekt und daher keine empfohlene Lösung.

1
Xavier Shay

Anstelle von obj.send können Sie auch eine Singleton-Methode verwenden. Es sind 3 weitere Codezeilen in Ihrer Testklasse und es sind keine Änderungen am zu testenden Code erforderlich.

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

In den Testfällen verwenden Sie dann my_private_method_publicly wann immer Sie testen möchten my_private_method.

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

obj.send für private Methoden wurde ersetzt durch send! in 1.9, aber später send! wurde wieder entfernt. So obj.send funktioniert einwandfrei.

1
Franz Hinkel

So korrigieren Sie die oben stehende Antwort: In Ruby 1.9.1 werden alle Nachrichten durch Object # send und Object # public_send unter Wahrung des Datenschutzes gesendet.

1
Victor K.

Um das tun zu können:

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

Sie können dies in Ihrer test_helper-Datei implementieren:

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
0
Knut Stenmark