Каков наилучший способ юнит-тестирования защищенных и приватных методов в Ruby? - PullRequest
128 голосов
/ 06 ноября 2008

Каков наилучший способ модульного тестирования защищенных и закрытых методов в Ruby с использованием стандартной среды Ruby Test::Unit?

Я уверен, что кто-то придумает и догматично скажет, что «вы должны только тестировать общедоступные методы, если требуется модульное тестирование, это не должен быть защищенный или закрытый метод», но меня это не очень интересует. обсуждая это. У меня есть несколько методов, которые являются защищенными или частными по уважительным и уважительным причинам, эти частные / защищенные методы умеренно сложны, и публичные методы в классе зависят от того, работают ли эти защищенные / частные методы правильно, поэтому Мне нужен способ проверить защищенные / приватные методы.

Еще одна вещь ... Обычно я помещаю все методы для данного класса в один файл, а модульные тесты для этого класса - в другой файл. В идеале я хотел бы, чтобы вся магия реализовала эту функциональность «модульного теста защищенных и закрытых методов» в файле модульного теста, а не в основном исходном файле, чтобы сделать основной исходный файл максимально простым и понятным.

Ответы [ 16 ]

131 голосов
/ 06 ноября 2008

Вы можете обойти инкапсуляцию с помощью метода отправки:

myobject.send(:method_name, args)

Это «особенность» Ruby. :)

Во время разработки Ruby 1.9 были внутренние дебаты, в которых рассматривалось, как send уважать конфиденциальность и send! игнорировать ее, но в итоге ничего не изменилось в Ruby 1.9. Игнорируйте комментарии ниже, обсуждая send! и ломая вещи.

71 голосов
/ 06 ноября 2008

Вот один простой способ, если вы используете RSpec:

before(:each) do
  MyClass.send(:public, *MyClass.protected_instance_methods)  
end
30 голосов
/ 06 ноября 2008

Просто заново откройте класс в своем тестовом файле и переопределите метод или методы как общедоступные. Вам не нужно переопределять внутренности самого метода, просто передайте символ в вызов public.

Если ваш исходный класс определен так:

class MyClass

  private

  def foo
    true
  end
end

В тестовом файле просто сделайте что-то вроде этого:

class MyClass
  public :foo

end

Вы можете передать несколько символов в public, если хотите предоставить больше частных методов.

public :foo, :bar
10 голосов
/ 06 ноября 2008

instance_eval() может помочь:

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

Вы можете использовать его для прямого доступа к закрытым методам и переменным экземпляра.

Вы также можете рассмотреть возможность использования send(), что также даст вам доступ к закрытым и защищенным методам (как предложил Джеймс Бейкер)

Кроме того, вы можете изменить метакласс вашего тестового объекта, чтобы сделать приватные / защищенные методы общедоступными только для этого объекта.

    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

Это позволит вам вызывать эти методы, не затрагивая другие объекты этого класса. Вы можете открыть класс в вашем тестовом каталоге и сделать его общедоступным для всех экземпляры в вашем тестовом коде, но это может повлиять на ваш тест открытого интерфейса.

9 голосов
/ 06 ноября 2008

Один способ, которым я делал это в прошлом:

class foo
  def public_method
    private_method
  end

private unless 'test' == Rails.env

  def private_method
    'private'
  end
end
7 голосов
/ 18 июля 2009

Я уверен, что кто-то подаст трубку и догматично утверждать, что "вы должны только общедоступные методы юнит-тестирования; если оно требуется юнит-тестирование, оно не должно быть защищенный или частный метод ", но я не очень заинтересован в обсуждении что.

Вы также можете преобразовать их в новый объект, в котором эти методы являются общедоступными, и делегировать им конфиденциально в исходном классе. Это позволит вам тестировать методы без магической метарубы в ваших спецификациях, сохраняя при этом их конфиденциальность.

У меня есть несколько методов, которые защищенный или частный для блага и уважительные причины

Что это за веские причины? Другие языки ООП могут вообще обходиться без частных методов (вспоминается smalltalk - когда частные методы существуют только как соглашение).

5 голосов
/ 04 марта 2016

Аналогично ответу @ WillSargent, вот что я использовал в блоке describe для особого случая тестирования некоторых защищенных валидаторов без необходимости проходить через сложный процесс их создания / обновления с помощью FactoryGirl (и вы можете использовать private_instance_methods аналогично):

  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 голосов
/ 25 ноября 2012

Чтобы опубликовать все защищенные и приватные методы для описанного класса, вы можете добавить следующее в ваш spec_helper.rb и не трогать какие-либо из ваших спецификационных файлов.

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 голосов
/ 17 февраля 2013

Я знаю, что опаздываю на вечеринку, но не проверяю частные методы .... Я не могу придумать причину сделать это. Публично доступный метод где-то использует этот приватный метод, тестирует публичный метод и различные сценарии, которые могут привести к использованию этого приватного метода. Что-то входит, что-то выходит. Тестирование приватных методов - это большое «нет-нет», и это усложняет процесс рефакторинга вашего кода позже. Они являются частными по причине.

3 голосов
/ 06 ноября 2008

Вы можете «открыть» класс и предоставить новый метод, который делегирует частному:

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