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

Sim*_*mon 3 testing unit-testing rspec puppet

我有一个使用自定义 Puppet 函数结果的 Puppet 类。为了确保在对类进行单元测试时只测试我的类中的逻辑,而不是我的函数中的逻辑,我想模拟该函数。

但是,我似乎无法将模拟函数完全隔离到单个上下文中。我真正的测试代码比下面的例子大,但我把它归结为:

class break_tests {

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

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

}
Run Code Online (Sandbox Code Playgroud)
class break_tests {

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

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

}
Run Code Online (Sandbox Code Playgroud)
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)>'
Run Code Online (Sandbox Code Playgroud)

我尝试将其拆分为两个describes 甚至两个单独的文件,结果始终相同:一个上下文接收来自不同上下文的输出。

在我更大的测试用例中,大约有 20 个测试,它甚至更加复杂,似乎受某些上下文是否具有分配给它们的事实的影响。上下文的排序似乎无关紧要。

我在这里缺少什么?

Ale*_*vey 5

在撰写本文时(Puppet 6.6.0,Rspec-puppet 2.7.5),不幸的是,模拟 Puppet 函数的整个业务仍然有点混乱。rspec-puppet 的文档仍然引用遗留的 Ruby API 函数,这无济于事。

您面临的问题正如 John Bollinger 在评论中所说,您有一个编译器实例,在加载 Rspec 文件时运行,然后在it稍后运行的块中进行断言。

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

  1. describecontext块在Rspec的文件被加载时间都进行评估。
  2. 这些it块,即示例本身,会被缓存并稍后进行评估。

在灯架上有一个答案由Rspec的公司在Stack Overflow笔者在这里,我建议有一个看看。

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

所以,你可以做什么?

选项 1 - 使用 Tom Poulton 的rspec-puppet-utils

这具有现成解决方案的优势,该解决方案通过众所周知的接口来模拟您的 Puppet 函数,并且使用expectedTom 实现的功能,您还可以在不同的示例中重新编译目录。

缺点可能是它使用 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
Run Code Online (Sandbox Code Playgroud)

选项 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
Run Code Online (Sandbox Code Playgroud)

选项 3 - 找到更好的方法

如果您在其他 Puppet 模块中搜索足够长的时间,您可能会找到更好的解决方案 - 即使是使用 Puppet 4 Function API 的解决方案。也就是说,我想对于您的测试目的来说并不重要,只要假函数返回您期望的响应即可。