Help us understand the problem. What is going on with this article?

Fluentdプラグイン開発(RSpec)

More than 1 year has passed since last update.

Fluentdのプラグイン開発について公式ドキュメントが充実してて良いのですが
RSpecの例は載って無さそうなので、いろいろ試行錯誤した内容を書きます。

プロジェクトスケルトン作成

$ fluent-plugin-generate filter leef

RSpecを利用するための準備

$ rspec --init

.rspecファイル、specディレクトリ、spec/spec_helper.rbの用意。

*.gemspec

spec.add_development_dependency "test-unit"
spec.add_development_dependency "rspec"
spec.add_development_dependency "rspec_junit_formatter"
spec.add_development_dependency "simplecov"
spec.add_runtime_dependency "fluentd", [">= 0.14.10", "< 2"]

test-unitは、テストドライバの部分で使われるので記述が必須。
rspec_junit_formatterは、CircleCIを使う際には必要となる。

Rakefile

スケルトンでは、test-unit用になっているのでRSpec用に以下の通りに書き換える。

# -*- coding: utf-8 -*-
# frozen_string_literal: true

require "bundler"
require "rspec/core/rake_task"

Bundler::GemHelper.install_tasks

RSpec::Core::RakeTask.new("spec")
task: default => :spec

spec/spec_helper.rb

# coverage計測用に、simplecovを読み込む。
require "simplecov"

# テスト用に`fluent/test`を読み込む。
require "fluent/test"

# `RSpec`実行時に`Test::Unit`の自動実行結果が表示されてしまうので、自動実行されないようにする。
Test::Unit::AutoRunner.need_auto_run = false if defined? Test::Unit::AutoRunner

# テスト用のヘルパーを読み込む
require "fluent/test/helpers"

# フィルタプラグイン用のテストドライバを読み込む(フィルタプラグイン作成時)
require "fluent/test/driver/filter"

# 開発するプラグインを読み込む
require "fluent/plugin/filter_leef"

RSpec.configure do |config|
  # Fluent::Test.setupを呼び出しテスト用の初期化処理を行う
  config.before(:all) { Fluent::Test.setup }

  以下略

.simplecov

RSpec実行時にsimplecovを動かす際の除外設定用。

# -*- coding: utf-8 -*-
# frozen_string_literal: true

SimpleCov.start do
  add_filter "/vendor/bundle/"
end

.rspec

--require spec_helper
--format documentation

本体コードの記述(例)

lib/flunet/plugin/filter_leef.rb

# -*- coding: utf-8 -*-
# frozen_string_literal: true

require "fluent/plugin/filter"

module Fluent
  module Plugin
    class FilterLEEF < Fluent::Plugin::Filter
      Fluent::Plugin.register_filter("leef", self)

      config_param :key_name1, :string
      config_param :key_name2, :string

      def configure(conf)
        super
      end

      def filter(tag, time, record)
        省略

        return filtered_record
      end

      def hello
        "hello"
      end
    end
  end
end

specの記述

spec/fluent/plugin/filter_leef_spec.rb

# -*- coding: utf-8 -*-
# frozen_string_literal: true

# RSpec.describeには、本体コードに書いたクラスを指定する
RSpec.describe Fluent::Plugin::FilterLEEF do
  include Fluent::Test::Helpers

  # フィルタ用のテストドライバの生成
  let(:driver) do
    Fluent::Test::Driver::Filter.new(described_class).configure(conf)
  end

  # ドライバのインスタンスを取り出す
  # 本体コードのクラスのインスタンスメソッドは、`filter.hogehoge(foobar)`で呼び出せる
  let(:filter) { driver.instance }

  # fluent.confの内容のフィルタに関する部分が`configure`メソッドを通して
  # フィルタプラグインに渡ってくる
  # その内容をconfとして用意する
  let(:conf) do
    %(
      key_name1 message1
      key_name2 message2
    )
  end

  # describeには、メソッドと引数を記述する
  # インスタンスメソッドの場合は、#を前置
  describe "#filter(tag, time, record)" do
    # filterメソッドのテストをする場合は、テストドライバを経由して
    # フィルタプラグインの動作を確認することになるので
    # driver.runおよびdriver.feed経由でfilterメソッドを呼び出し、
    # dirver.filtered_recordsメソッド呼び出しの結果に含まれる
    # 値をチェックする
    subject { driver.filtered_records.first }

    let(:record) { {"message" => "hogehoge"} }

    before do
      driver.run(default_tag: "test") { driver.feed(Time.now.to_i, record) }
    end

    it { is_expected.to eq "fugafuga" }
  end

  describe "#hello" do
    # filterメソッド以外のインスタンスメソッドのテストをしたい場合は
    # テストドライバから取り出したインスタンス(この場合はfilterインスタンス)の
    # インスタンスメソッド(この場合はhelloメソッド)を呼んでテストする
    subject { filter.hello }

    it { is_expected.to eq "hello" }
  end
end

fluent.confに以下の記述がある場合。

<filter foo.*>
  @type leef
  key_name1 message1
  key_name2 message2
</filter>

confとして以下の内容が渡ってくる。

"key_name1 message1\nkey_name2 message2"

テストコード上のconfも同様に文字列を用意すればよいが
%記法を使うと記述が少し書きやすくなる。

let(:conf) do
  %(
    key_name1 message1
    key_name2 message2
  )
end

fluentdのログ出力内容を確認したい場合

本体コード内でlog.error "hogehoge"など、
fluentdのログに出力する箇所があり、
ログ出力内容の確認を行いたい場合は次のようにする。

describe "#sample" do
  subject { dirver.logs }

  before do
    filter.sample
  end

  it { is_expected.to include(include("hogehoge")) }
end

RSpec実行方法

プロジェクトルートで。

bundlerを使って関連パッケージの用意。

$ bundle install --path vendor/bundle 

bundler経由でrspecを実行

$ bundle exec rspec

参考

https://docs.fluentd.org/plugin-development/plugin-test-code

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away