unitarias spec rails pruebas describe ruby unit-testing private protected

spec - ¿Cuál es la mejor manera de probar la unidad de métodos protegidos y privados en Ruby?



pruebas unitarias ruby (16)

Estoy seguro de que alguien insistirá y dirá dogmáticamente que "usted solo debe probar los métodos públicos de la unidad, si necesita pruebas unitarias, no debe ser un método privado o protegido", pero no estoy realmente interesado en debatir eso.

También podría refactorizarlos en un nuevo objeto en el que esos métodos sean públicos y delegarlos de forma privada en la clase original. Esto le permitirá probar los métodos sin metaruby mágico en sus especificaciones sin dejar de mantenerlos privados.

Tengo varios métodos que están protegidos o privados por buenas y válidas razones

¿Cuáles son esas razones válidas? Otros lenguajes OOP pueden escaparse sin métodos privados en absoluto (Smalltalk viene a la mente, donde los métodos privados solo existen como una convención).

¿Cuál es la mejor manera de probar las unidades de métodos protegidos y privados en Ruby, utilizando el marco estándar de Ruby Test::Unit ?

Estoy seguro de que alguien insistirá y dirá dogmáticamente que "usted solo debe probar los métodos públicos de la unidad, si necesita pruebas unitarias, no debe ser un método privado o protegido", pero no estoy realmente interesado en debatir eso. Tengo varios métodos que están protegidos o son privados por buenas y válidas razones, estos métodos privados / protegidos son moderadamente complejos, y los métodos públicos de la clase dependen de que estos métodos protegidos / privados funcionen correctamente, por lo tanto, necesito una forma de probarlos. los métodos protegidos / privados.

Una cosa más ... Generalmente pongo todos los métodos para una clase dada en un archivo, y la unidad prueba para esa clase en otro archivo. Idealmente, me gustaría que toda la magia implementara esta funcionalidad de "prueba unitaria de métodos privados y protegidos" en el archivo de prueba de la unidad, no en el archivo fuente principal, a fin de mantener el archivo fuente principal lo más simple y directo posible.


Aquí hay una adición general a Class que uso. Es un poco más inteligente que solo hacer público el método que estás probando, pero en la mayoría de los casos no importa, y es mucho más legible.

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

El uso de enviar para acceder a métodos protegidos / privados está roto en 1.9, por lo que no es una solución recomendada.


Aquí hay una manera fácil si usa RSpec:

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


En Test :: unidad marco puede escribir,

MyClass.send(:public, :method_name)

Aquí "method_name" es un método privado.

y mientras llamas a este método puedes escribir,

assert_equal expected, MyClass.instance.method_name(params)


En lugar de obj.send puedes usar un método singleton. Son 3 líneas más de código en su clase de prueba y no requiere cambios en el código real para ser probado.

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

En los casos de prueba, entonces usa my_private_method_publicly siempre que quiera probar my_private_method .

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

obj.send para métodos privados fue reemplazado por send! en 1.9, pero luego send! fue eliminado de nuevo. Entonces obj.send funciona perfectamente bien.


Para corregir la respuesta superior anterior: en Ruby 1.9.1, es Object # send que envía todos los mensajes, y Object # public_send que respeta la privacidad.


Para hacer esto:

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

Puede implementar esto en su archivo test_helper:

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


Para hacer público todo el método protegido y privado para la clase descrita, puede agregar lo siguiente a su spec_helper.rb y no tener que tocar ninguno de sus archivos de especificaciones.

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


Probablemente me inclinaría hacia el uso de instance_eval (). Sin embargo, antes de saber sobre instance_eval (), crearía una clase derivada en mi archivo de prueba de unidad. Luego establecería los métodos privados para que sean públicos.

En el siguiente ejemplo, el método build_year_range es privado en la clase PublicationSearch :: ISIQuery. Obtener una nueva clase solo para fines de prueba me permite configurar un método para que sea público y, por lo tanto, directamente comprobable. Del mismo modo, la clase derivada expone una variable de instancia llamada ''resultado'' que anteriormente no estaba expuesta.

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

En mi prueba unitaria tengo un caso de prueba que ejemplifica la clase MockISIQuery y prueba directamente el método build_year_range ().


Puede "reabrir" la clase y proporcionar un nuevo método que delegue en la clase privada:

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


Puede omitir la encapsulación con el método de envío:

myobject.send(:method_name, args)

Esta es una ''característica'' de Ruby. :)

¡Hubo un debate interno durante el desarrollo de Ruby 1.9 que consideró send privacidad del respeto y send! ignóralo, pero al final nada cambió en Ruby 1.9. Ignora los comentarios a continuación discutiendo send! y rompiendo cosas


Sé que llego tarde a la fiesta, pero no pruebo métodos privados ... No puedo pensar en una razón para hacer esto. Un método de acceso público es usar ese método privado en alguna parte, probar el método público y la variedad de escenarios que causarían que se use ese método privado. Algo entra, sale algo. Probar métodos privados es un gran no-no, y hace que sea mucho más difícil refactorizar tu código más tarde. Son privados por una razón.


Similar a la respuesta de @ WillSargent, esto es lo que he usado en un bloque de describe para el caso especial de probar algunos validadores protegidos sin tener que pasar por el proceso pesado de creación / actualización con FactoryGirl (y podría usar private_instance_methods manera similar):

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


Simplemente vuelva a abrir la clase en su archivo de prueba y redefina el método o los métodos como públicos. No tiene que redefinir las agallas del método en sí, simplemente pase el símbolo a la llamada public .

Si tu clase original se define así:

class MyClass private def foo true end end

En su archivo de prueba, solo haga algo como esto:

class MyClass public :foo end

Puede pasar múltiples símbolos a public si quiere exponer más métodos privados.

public :foo, :bar


Una forma en que lo hice en el pasado es:

class foo def public_method private_method end private unless ''test'' == Rails.env def private_method ''private'' end end


instance_eval() podría ayudar:

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

Puede usarlo para acceder a métodos privados y variables de instancia directamente.

También podría considerar el uso de send() , que también le dará acceso a métodos privados y protegidos (como sugirió James Baker)

Alternativamente, podría modificar la metaclase de su objeto de prueba para hacer que los métodos privados / protegidos sean públicos solo para ese objeto.

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

Esto le permitirá llamar a estos métodos sin afectar a otros objetos de esa clase. Puede volver a abrir la clase dentro de su directorio de prueba y hacerla pública para todas las instancias dentro de su código de prueba, pero eso podría afectar su prueba de la interfaz pública.