使用 RSpec 和 PDK 测试内部 class 是否存在

Test an inner class exists with RSpec and PDK

我有一个相当基本的网络服务木偶模块 运行 tomcat。我想在 Tomcat 的 catalina.out 文件上设置 logrotate,我想首先编写一个测试来确认 logrotate 包含在模块中并使用正确的设置进行设置。

这是我的 webservice.pp 的精简版,例如:

class my_module::webservice (
  ...
){
  include ::tomcat_server

  ...

  logrotate::rule { 'tomcat':
    path          => '/var/log/tomcat/catalina.out',
    rotate        => 1,
    rotate_every  => 'day',
    copytruncate  => true,
    missingok     => true,
    compress      => true,
    delaycompress => true,
  }
}

并且我在我的 .fixtures.yml 中包含了 logrotate forge 模块,如下所示:

fixtures:
  forge_modules:
    logrotate:
      repo: 'puppet-logrotate'
      ref:  '3.2.1'
    ...

但我只能编写一个测试来确认 logrotate 包含在模块中,如下所示:

require 'spec_helper'

describe 'my_module::webservice' do
  on_supported_os.each do |os, os_facts|
    context "on #{os}" do
      let(:facts) { os_facts }

      it { is_expected.to compile }

      it { is_expected.to contain_class('logrotate') }
    end
  end
end

这不起作用(如果我从 init.pp 中删除 logrotate 块,那么测试仍然通过):

it { is_expected.to contain_class('logrotate::conf') }

也不要求 with:

it { is_expected.to contain_class('logrotate') \
  .with('path'          => '/var/log/tomcat/catalina.out',
        'rotate'        => 1,
        'rotate_every'  => 'day',
        'copytruncate'  => true,
        'missingok'     => true,
        'compress'      => true,
        'delaycompress' => true,
  )
}

separate/nested describe 块也没有:

describe 'logrotate::rule' do
  let(:title) { 'tomcat' }
  let(:params) do
    {
        'path'          => '/var/log/tomcat/catalina.out',
        'rotate'        => 1,
        'rotate_every'  => 'day',
        'copytruncate'  => true,
        'missingok'     => true,
        'compress'      => true,
        'delaycompress' => true,
    }
  end
end

我在 rspec 文档中找不到任何提及除测试 class 已定义之外的任何内容。甚至可以做我想做的事吗?

这是我的目录布局:

puppet
  `- modules
        `- my_module
             |- data
             |- manifests
             |    |- init.pp
             |    `- webservice.pp
             |- spec
             |    |- classes
             |    |    `- webservice_spec.rb
             |    `- spec_helper.rb
             |- .fixtures.yml
             |- Gemfile
             |- hiera.yaml
             |- metadata.json
             `- Rakefile

I have a fairly basic puppet module for a webservice running tomcat. I want to setup logrotate on Tomcat's catalina.out file, and I want to start by writing a test that confirms logrotate is included in the module and setup with the correct settings.

听起来很有道理。然而,这...

Here's a stripped down version of my init.pp, for example:

class my_module::webservice (
  ...
){

...充其量是一种糟糕的做法。如果它存在,那么模块 my_moduleinit.pp 清单应该只定义 class my_module。名为 my_module::webservice 的 class 应该改为在模块 my_module 中名为 webservice.pp 的清单中定义。 Puppet 在线文档中对模块布局的期望是 documented。虽然您可能能够避免与这些规范存在某些差异,但这样做只有一个缺点。

在这一点上,我发现 "inner class" 不是惯用的 Puppet 术语,它表明对您正在使用的内容存在误解。具体来说,这个 ...

logrotate::rule { 'tomcat':

[...]

... 根本不声明 class,而是声明类型为 [=23 的 resource =],这显然是 puppet/logrotate 模块提供的定义类型。通常,声明资源并不意味着来自提供资源类型的模块(如果有)的 classes 的任何内容。

此外,虽然声明 logrotate::rule 资源完全有可能导致 class logrotate 也包含在目录中,但这将是 logrotate::rule,因此,您的规格测试不应该测试它。只有当 my_module::webservice 预期自己声明 class logrotate 时,它的测试才应该对此进行检查。

你接着说:

This doesn't work (if I remove the logrotate block from init.pp then the tests still pass):

it { is_expected.to contain_class('logrotate::conf') }

您没有为我们提供足够的代码来确定为什么当测试包含在其中时测试会通过,但是如果期望得到满足,那就很奇怪了。 logrotate::conf 也是定义的(资源)类型,而不是 class,因此期望应该 永远不会 成功。按照我上面介绍的主题,如果 class my_module::webservice 没有直接声明任何 logrotate::conf 资源,那么它的测试不应该检查一个。

nor does asking for with:

it { is_expected.to contain_class('logrotate') \
  .with('path'          => '/var/log/tomcat/catalina.out',
        'rotate'        => 1,
        'rotate_every'  => 'day',
        'copytruncate'  => true,
        'missingok'     => true,
        'compress'      => true,
        'delaycompress' => true,
  )
}

当然不会成功。它表达了对 class logrotate 声明的期望,但您实际声明的是 logrotate::rule 类型的资源。即使 logrotate::rule 确实声明了 logrotate,也不会期望它传递自己的参数列表。

and nor does a separate/nested describe block:

describe 'logrotate::rule' do

[...]

同样,这并不奇怪。这样一个 describe 块告诉 RSpec logrotate::rule 是被测的 class。它不仅不是被测试的 class(当然是 my_module::webservice),而且 logrotate::rule 根本不是 class。 RSpec 当然也可以测试定义的类型,但这不是您想要的。

为了测试一个资源是否被测试class所声明,我们使用contain_类型的谓词(title),其中类型名称中的任何命名空间分隔符 (::) 都将替换为双下划线。例如:

it do
  is_expected.to contain_logrotate__rule('tomcat')
end

允许但可选地包含一个或多个 with 子句来指定对指定资源的声明参数的期望。按照你似乎一直在尝试做的事情,那么,也许这会更充分地表达你正在寻找的东西:

require 'spec_helper'

describe 'my_module::webservice' do
  on_supported_os.each do |os, os_facts|
    context "on #{os}" do
      let(:facts) { os_facts }

      it do
        is_expected.to compile
        is_expected.to contain_logrotate__rule('tomcat')
          .with(
            path: '/var/log/tomcat/catalina.out',
            rotate: 1,
            rotate_every: 'day',
            copytruncate: true,
            missingok: true,
            compress: true,
            delaycompress: true
          )
      end
    end
  end
end

请注意,顺便说一句,当您想针对同一个示例测试多个谓词时,将它们组合在同一个 it 块中的效率要高得多,如上所示,将每个放在自己的 it 块中。同样,您可能会注意到测试 运行 时间的差异,即使将两个 it 块合并为一个也是如此。

此外,我上面的示例演示的编码风格接近于避免来自 pdk validate 的警告所需的编码风格,这给我们带来了额外的一点:验证 pdk validate 是否完成而没有错误总是有用的或尝试单元测试之前的警告。您可能会发现它对 Puppet 和 Ruby 代码风格过于挑剔,但它也会发现一些导致神秘测试失败的问题。此外,它的运行速度比测试快得多,而且它几乎可以发现 Puppet 和 Ruby 代码中的所有语法错误。令人沮丧的是,由于一个小的语法错误,您的测试需要很长时间才能失败。