如何在rspec单元测试中隔离Puppet函数模拟

时间:2019-07-02 12:31:13

标签: unit-testing testing rspec puppet

我有一个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 个答案:

答案 0 :(得分:3)

在撰写本文时(Puppet 6.6.0,Rspec-puppet 2.7.5),不幸的是,模拟Puppet函数的整个过程仍然有些混乱。 rspec-puppet的docs仍然引用旧版Ruby API的功能并没有帮助。

您所面对的问题就是约翰·博林格(John Bollinger)在评论中所说的,您有一个编译器实例,该实例在加载Rspec文件时运行,然后在it块中的断言稍后运行。 / p>

请记住,Rspec(Rspec本身,与Puppet无关)运行分为两个阶段:

  1. describecontext块在加载Rspec文件时均进行评估。
  2. it块(示例本身)将在以后进行缓存和评估。

Rspec的作者在Stack Overflow here上对此有一个答案,我建议看看。

因此,为了避免为每个示例编译目录(这会使Rspec-puppet太慢),编译将在执行it示例之前进行缓存。

那你能做什么?

选项1-使用汤姆·普尔顿的rspec-puppet-utils

这具有现成解决方案的优点,该解决方案可以通过众所周知的界面来模拟您的Puppet函数,并使用Tom实现的expected功能,还可以使目录重新编译到其中。不同的例子。

缺点可能是它使用Mocha而不是Rspec-mocks,它使用传统的Ruby API-但是Rspec-puppet的文档也是如此! -自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-窃取Tom的一些代码-猴子补丁Rspec-puppet

然而,在幕后,Tom的代码只是对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模块中搜索足够长的时间,则可能会找到更好的解决方案-甚至是使用Puppet 4 Function API的解决方案。就是说,我认为对于您的测试而言,只要假函数返回您期望的响应就没关系。