Статьи

Кукольный юнит, как профессионал

Большое спасибо Atlassian за предоставленную мне возможность опубликовать этот сериал !!

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

До тех пор, пока мы не узнаем, это означает, что у нас есть, только если компилятор доволен , а не выполняет функцию, которая ему необходима. В 2009 году, после первых дней занятий, я написал коллекцию ссылок, управляемых тестовой инфраструктурой . Это было очевидно вдохновлено разговором Линдсей Холмвуд о огурец-нагио .

На фронте шеф-повара Opscode Стивен Нельсон-Смит написал отличную книгу о том, как это сделать, с помощью Chef . Также см. Проект cuken, где сгруппированы повторно используемые шаги огурца.

Поскольку мы используем Puppet здесь, в Atlassian, я не мог понять текущее состояние тестирования кукол. Многое уже можно найти по адресу http://puppetlabs.com/blog/testing-modules-in-the-puppet-forge/

Обратите внимание, что я специально назвал этот блог «Кукольным модульным тестированием», так как тесты, которые я сейчас описываю, не работают на реальной системе. Поэтому трудно проверить реальное поведение.


Совет 1: огурец-марионетка

Вдохновленный рассказом Линдси Холмвуд о огурец-нагио и манекенщицей Охада Леви, Николай Штурм создал кукольную куклу

В своем посте « Мысли о тестировании кукольных манифестов» он объясняет, что идея написания тестов НЕ заключается в дублировании кода, и он определил наиболее распространенные проблемы, с которыми он сталкивается:

  • каталог не компилируется: синтаксические ошибки, отсутствующие файлы шаблона, ..
  • каталог компилируется, но не может быть применен: недоступные или несуществующие ресурсы, недостающие файловые ресурсы в репо
  • Каталог действительно применяется, но он неисправен: дефектные файлы, из-за пустых переменных манифеста или неправильных значений, отсутствующих зависимостей (неправильный порядок …), файлы устанавливаются без обеспечения каталога …

Важный совет:

Спецификации ресурса могут быть полезны для целей документации или рефакторинга. Тем не менее, существует риск повторной реализации манифеста Puppet, так что будьте осторожны.

$ cd puppet-mymodule
$ gem install cucumber-puppet

Напишите функции для каждого модуля, это структура, к которой мы стремимся:

module
  +-- manifests
  +-- lib
  +-- features
       +-- support
       |     +-- hooks.rb
       |     +-- world.rb
       +-- catalog
       +-- feature..

Создайте огуречно-кукольный мир:

$ cucumber-puppet-gen world
Generating with world generator:
     [ADDED]  features/support/hooks.rb
     [ADDED]  features/support/world.rb
     [ADDED]  features/steps

# Adjust the paths to your modules and manifests
$ cat features/support/hooks.rb
Before do
  # adjust local configuration like this
  # @puppetcfg['confdir']  = File.join(File.dirname(__FILE__), '..', '..')
  # @puppetcfg['manifest'] = File.join(@puppetcfg['confdir'], 'manifests', 'site.pp')
  # @puppetcfg['modulepath']  = "/srv/puppet/modules:/srv/puppet/site-modules"

  # adjust facts like this
  @facts['architecture'] = "i386"
end

# Nothing exciting here
$ cat features/support/world.rb

require 'cucumber-puppet/puppet'
require 'cucumber-puppet/steps'

World do
  CucumberPuppet.new
end

Создание функции политики:

$ cucumber-puppet-gen policy
Generating with policy generator:
     [ADDED]  features/catalog

# Notice the <hostname>.example.com.yaml
# These files contain the facts to test your catalog against
# 
$ cat features/catalog/policy.feature 
Feature: General policy for all catalogs
  In order to ensure applicability of a host's catalog
  As a manifest developer
  I want all catalogs to obey some general rules

  Scenario Outline: Compile and verify catalog
    Given a node specified by "features/yaml/<hostname>.example.com.yaml"
    When I compile its catalog
    Then compilation should succeed
    And all resource dependencies should resolve

    Examples:
      | hostname  |
      | localhost |

Для фактического запуска:

$ cucumber-puppet features/catalog/policy.feature 
Feature: General policy for all catalogs
  In order to ensure applicability of a host's catalog
  As a manifest developer
  I want all catalogs to obey some general rules

  Scenario Outline: Compile and verify catalog                            # features/catalog/policy.feature:6
    Given a node specified by "features/yaml/<hostname>.example.com.yaml" # cucumber-puppet-0.3.6/lib/cucumber-puppet/steps.rb:1
    When I compile its catalog                                            # cucumber-puppet-0.3.6/lib/cucumber-puppet/steps.rb:14
    Then compilation should succeed                                       # cucumber-puppet-0.3.6/lib/cucumber-puppet/steps.rb:48
    And all resource dependencies should resolve                          # cucumber-puppet-0.3.6/lib/cucumber-puppet/steps.rb:28

    Examples: 
      | hostname  |
      | localhost |
      Cannot find node facts features/yaml/localhost.example.com.yaml. (RuntimeError)
      features/catalog/policy.feature:7:in `Given a node specified by "features/yaml/<hostname>.example.com.yaml"'

Failing Scenarios:
cucumber features/catalog/policy.feature:6 # Scenario: Compile and verify catalog

1 scenario (1 failed)
4 steps (1 failed, 3 skipped)
0m0.006s

Список команд:

Generators for cucumber-puppet

Available generators
    feature                          Generate a cucumber feature
    policy                           Generate a catalog policy
    testcase                         Generate a test case for the test suite
    testsuite                        Generate a test suite for puppet features
    world                            Generate cucumber step and support files

General options:
    -p, --pretend                    Run, but do not make any changes.
    -f, --force                      Overwrite files that already exist.
    -s, --skip                       Skip files that already exist.
    -d, --delete                     Delete files that have previously been generated with this generator.
        --no-color                   Don't colorize the output
    -h, --help                       Show this message
        --debug                      Do not catch errors

Он также добавил поддержку для тестирования exported-ресурсов .

А для более практического объяснения посмотрите, как Оливер Хокинс описывает, как Nokia использует огуречную марионетку

Scenario: Proxy host and port have sensible defaults
  Given a node of class "mymodule::myapp"
  And we have loaded "test" settings
  And we have unset the fact "proxy_host"
  And we have unset the fact "proxy_port"
  When I compile the catalog
  Then there should be a file "/etc/myapp/config.properties"
  And the file should contain "proxy.port=-1"
  And the file should contain /proxy\.host=$/

----

Then /^the file should contain "(.*)"$/ do |text|
  fail "File parameter 'content' was not specified" if @resource["content"].nil?
  fail "Text content [#{text}] was not found" unless @resource["content"].include?(text)
end

Then /^the file should contain \/([^\"].*)\/$/ do |regex|
  fail "File parameter 'content' was not specified" if @resource["content"].nil?
  fail "Text regex [/#{regex}/] did not match" unless @resource["content"] =~ /#{regex}/
end



Совет 2: rspec-puppet

Хотя идея использования specs и puppet не нова ( https://github.com/jes5199/puppet_spec ), новый инструмент в блоке — rspec-puppet, предложенный нам Тимом Шарпом . Тот же человек, который дал нам vim-puppet и puppet-lint

Как и в случае структуры cucumber-puppet, идея заключается в том, чтобы каталог specs был близок к вашему модулю:

module
  +-- manifests
  +-- lib
  +-- spec
       +-- spec_helper.rb
       +-- classes
       |     +-- <class_name>_spec.rb
       +-- defines
       |     +-- <define_name>_spec.rb
       +-- functions
             +-- <function_name>_spec.rb

Я нашел полезным изменить по умолчанию spec_helper.rb по умолчанию

require 'rspec-puppet'

RSpec.configure do |c|
   c.module_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
   c.manifest_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', '..','..','manifests'))
end


desc "Run specs check on puppet manifests"
RSpec::Core::RakeTask.new(:spec) do |t|
   t.pattern = './demo-puppet/modules/**/*_spec.rb' # don't need this, it's default
   t.verbose = true
   t.rspec_opts = "--format documentation --color"
    # Put spec opts in a file named .rspec in root
  end

Вот быстрый пример проверки, устанавливает ли класс apache пакет httpd в системе Debian.

require "#{File.join(File.dirname(__FILE__),'..','spec_helper')}"

describe 'apache', :type => :class do
  let (:title { 'basic' })
  let(:params) { { } }
  let(:facts) { {:operatingsystem => 'Debian', :kernel => 'Linux'} }

  it { should contain_package('httpd').with_ensure('installed') }
end

Более подробное описание можно найти на

Для получения более общей информации о rspec:

Вывод огурец-марионетка против rspec-puppet

Я думаю, что вы можете написать свои тесты в обоих, чтобы сделать то же самое. В настоящее время они оба поддерживают 2.6 и 2.7

Я нашел rspec-puppet немного проще для манипуляции с такими параметрами, как: имя или: факты. Файл yaml не показался мне гибким. Также огурец, кажется, устанавливает больше зависимых драгоценных камней, которые могут нанести вред другим проектам.

Но как уже сказал Николай:

 

«Не дублируйте свои манифесты в своих тестах» Сосредоточьтесь на проблемах каталога, которые он описал ранее, и проверьте свою логику. Не проверяйте, выполняет ли кукол свою работу, проверяйте, что ваша логика выполняет свою работу.

Вот почему я назвал их юнит-тестами, они не тестируют реальную функциональность. (Это для следующего поста в блоге)


Совет 3: кукольный линт

Чтобы проверить ваши файлы на предмет стиля программирования, вы можете использовать https://github.com/rodjek/puppet-lint . Он проверит правила для интервалов, идентификаторов и пробелов, цитирования, ресурсов, условий, классов

Простой способ интегрировать его в ваш Rakefile:

require 'puppet-lint'

desc "Run lint check on puppet manifests"
task :lint do
linter =  PuppetLint.new
  Dir.glob('./demo-puppet/modules//**/*.pp').each do |puppet_file|
    puts "Evaluating #{puppet_file}"
    linter.file = puppet_file
    linter.run
  end
  fail if linter.errors?
 end

Теперь вы можете просто запустить:

$ rake lint

Совет 4: сойти с ума и создать собственную логику тестирования / каталога

Посмотрев на логику rspec-puppet, я посмотрел глубже, как пройти через объект каталога. Это в значительной степени работа в процессе, но идея состоит в том, чтобы найти способ посмотреть на изменения в каталоге.

Ниже приведен список полезных примеров понимания того, как работать с puppet в коде ruby:

Первый список ссылок — это несколько забавных инструментов, написанных Дином Уилсоном из известной www.puppetcookbook.com :

RI Pienaar из Mcollective Fame демонстрирует способ создания различий в каталоге. это может быть полезно, чтобы понять, какие тесты нужно запускать между изменениями:

В этой последней главе показано, как пройти по каталогу и проверить доступные классы и ресурсы:

https://gist.github.com/1430062#file_puppet_demo.rb

Источник:
http://www.jedi.be/blog/2011/12/05/puppet-unit-testing-like-a-pro/