Как изолировать насмешку над функциями Puppet в модульных тестах rspec - PullRequest
2 голосов
/ 02 июля 2019

У меня есть класс Puppet, который использует результат пользовательской функции Puppet. Чтобы убедиться, что я проверяю только логику в своем классе, а не логику в своей функции при выполнении модульных тестов для класса, я хочу смоделировать функцию.

Однако я не могу полностью изолировать мою макетированную функцию от одного контекста. Мой реальный код тестирования больше, чем в следующем примере, но я свел его к следующему:

class break_tests {

  $result = my_mocked_function('foo', 'bar', 'baz')

  file { 'under_test':
    content => $result,
  }

}
require 'spec_helper'

def mock_mmf(return_value)
  Puppet::Parser::Functions.newfunction(:'my_mocked_function', type: :rvalue) do |_args|
    return return_value
  end
end

# rubocop:disable Metrics/BlockLength
describe 'break_tests' do
  context 'numero uno' do
    before { mock_mmf('foo') }
    it { should contain_file('under_test').with_content('foo') }
  end
  context 'numero duo' do
    before { mock_mmf('bar') }
    it { should contain_file('under_test').with_content('bar') }
  end
end
Failures:

  1) break_tests numero duo should contain File[under_test] with content  supplied string
     Failure/Error: it { should contain_file('under_test').with_content('bar') }
       expected that the catalogue would contain File[under_test] with content set to supplied string
     # ./spec/classes/break_tests_spec.rb:17:in `block (3 levels) in <top (required)>'

Я попытался разбить его на два describe и даже два отдельных файла, результат всегда одинаков: один контекст получает выходные данные из другого контекста.

В моем более крупном тестовом примере, содержащем около 20 тестов, он еще более сложен, по-видимому, под влиянием того, имеют ли некоторые контексты присвоенные им факты. Упорядочение контекстов, кажется, не имеет значения.

Что мне здесь не хватает?

1 Ответ

3 голосов
/ 03 июля 2019

На момент написания статьи (Puppet 6.6.0, Rspec-puppet 2.7.5) весь бизнес насмешливых функций Puppet, к сожалению, остается беспорядочным.Это не поможет тому, что rspec-puppet docs по-прежнему ссылается на устаревший Ruby API для функций.

Проблема, с которой вы сталкиваетесь, заключается в том, что, как сказал Джон Боллинджер в комментариях,у вас есть экземпляр компилятора, который запускается при загрузке файлов Rspec, а затем утверждения в it блоках, которые запускаются позже.

Помните, что Rspec (сам Rspec, ничего общего с Puppet) работает в два этапа:

  1. Все блоки describe и context оцениваются во время загрузки файлов Rspec.
  2. Блоки it, сами примеры, кэшируются и оцениваютсяпозже.

На это есть ответ автора Rspec на Stack Overflow здесь , который я рекомендую посмотреть.

Итак, чтобы избежать каталогабудучи скомпилированным для каждого отдельного примера - что сделало бы Rspec-puppet слишком медленным - компиляция кэшируется до выполнения it примеров.

Так что вы можете сделать?

Option1 - Используйте Tom Poulton's rspec-puppet-utils .

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

Недостатки могут заключаться в том, что он использует Mocha, а не Rspec-mocks, он использует устаревший API Ruby - но затем то же самое делает Rspec.Куклы-куклы!- и это не было принято с 2017 года.

Таким образом, вы можете переписать свои тесты следующим образом:

require 'spec_helper'
require 'rspec-puppet-utils'

def mock_mmf(return_value)
  MockFunction.new('my_mocked_function').expected.returns(return_value)
end

describe 'test' do
  context 'numero uno' do
    before { mock_mmf('foo') }
    it { should contain_file('under_test').with_content('foo') }
  end
  context 'numero duo' do
    before { mock_mmf('bar') }
    it { should contain_file('under_test').with_content('bar') }
  end
end

Вариант 2 - Украсть часть кода Тома - патч обезьяны Rspec-puppet

Однако внутри кода Тома просто обезьяна исправляет Rspec-puppet, и вы можете просто украсть немного, что делает это, и рефакторировать ваши примеры следующим образом:

require 'spec_helper'
require 'rspec-puppet/cache'

module RSpec::Puppet  ## Add this block
  module Support
    def self.clear_cache
      @@cache = RSpec::Puppet::Cache.new
    end
  end
end

def mock_mmf(return_value)
  RSpec::Puppet::Support.clear_cache  ## ... and this line
  Puppet::Parser::Functions.newfunction(:'my_mocked_function', type: :rvalue) do |_args|
    return return_value
  end
end

describe 'test' do
  context 'numero uno' do
    before { mock_mmf('foo') }
    it { should contain_file('under_test').with_content('foo') }
  end
  context 'numero duo' do
    before { mock_mmf('bar') }
    it { should contain_file('under_test').with_content('bar') }
  end
end

Вариант 3 -найти лучший способ

Если вы будете долго искать в других модулях Puppet, вы можете найти лучшее решение - даже решения, использующие API функций Puppet 4.Тем не менее, я думаю, это не имеет большого значения для целей вашего теста, если поддельная функция возвращает ожидаемый вами ответ.

...