tl;dr
マージされるか解りませんが、awspec で CloudWatch Logs のテストをしたかったので、CloudWatch Logs リソースタイプの追加をプルリクエストした際の作業内容をメモっておきます。用語の使い方や認識に誤りがあり嘘を書いてしまっていることがあるかもしれませんがご容赦ください。また、ご指摘頂ければ幸いです。
awspec とは
awspec は福岡生まれ*1、福岡育ちの AWS 上で構築したリソースを Rspec の DSL でテストするツールです。
github.com
まさに地産地消(消費されているのは地元だけではなく世界ですが。)
自分の Rspec の知識は以下の記事くらいです。
inokara.hateblo.jp
プルリクエストして学ぶ awspec
テストしたい AWS リソース
テスト
一応、以下のように書くことを想定しています。
require 'spec_helper'
describe cloudwatch_logs('my-cloudwatch-logs-group') do
it { should exist }
its(:retention_in_days) { should eq 365 }
it { should have_log_stream('my-cloudwatch-logs-stream') }
it { should have_metric_filter('my-cloudwatch-logs-metric-filter') }
it do
should have_subscription_filter('my-cloudwatch-logs-subscription-filter')\
.filter_pattern('[host, ident, authuser, date, request, status, bytes]')
end
end
awspec では以下の資料のように、リソースタイプを追加しやすいツールが同梱されています。
speakerdeck.com
新しいリソースタイプを追加したい場合には、以下のように実行することで雛形が生成されるので、雛形を修正していくだけで新しいリソースタイプを追加することが出来ます。
bundle exec bin/toolbox template cloudwatch_logs
以下のように出力されます。
$ bundle exec bin/toolbox template cloudwatch_logs
+ lib/awspec/stub/cloudwatch_logs.rb
+ lib/awspec/type/cloudwatch_logs.rb
+ spec/type/cloudwatch_logs_spec.rb
+ lib/awspec/generator/doc/type/cloudwatch_logs.rb
+ doc/_resource_types/cloudwatch_logs.md
Generate CloudwatchLogs template files.
* !! AND add 'cloudwatch_logs' to Awspec::Helper::Type::TYPES in lib/awspec/helper/type.rb *
* !! AND add 'cloudwatch_logs' client to lib/awspec/helper/finder.rb *
Stub
Rspec での Stub とは「あるメソッドが呼ばれたら、任意の値を返す」為に利用されるものという理解。もっとシンプルに言うと、ダミーデータを返すものだと思っておけば良いと勝手に思っています。今回のプルリクエストでは以下のように書きました。
Aws.config[:cloudwatchlogs] = {
stub_responses: {
describe_log_groups: {
log_groups: [
{
log_group_name: 'my-cloudwatch-logs-group',
retention_in_days: 365
}
]
},
describe_log_streams: {
log_streams: [
{
log_stream_name: 'my-cloudwatch-logs-stream'
}
]
},
describe_metric_filters: {
metric_filters: [
{
filter_name: 'my-cloudwatch-logs-metric-filter'
}
]
},
describe_subscription_filters: {
subscription_filters: [
{
filter_name: 'my-cloudwatch-logs-subscription-filter',
filter_pattern: '[host, ident, authuser, date, request, status, bytes]'
}
]
}
}
}
ちなみに、AWS SDK for Ruby では以下の Blog 記事のように標準で Stub が呼べるようになっているとのことです。
ほえー。
Spec
ここでの Spec は awspec を利用する際に書くテストと同じ内容を記載する。
require 'spec_helper'
Awspec::Stub.load 'cloudwatch_logs'
describe cloudwatch_logs('my-cloudwatch-logs-group') do
it { should exist }
its(:retention_in_days) { should eq 365 }
it { should have_log_stream('my-cloudwatch-logs-stream') }
it { should have_metric_filter('my-cloudwatch-logs-metric-filter') }
it do
should have_subscription_filter('my-cloudwatch-logs-subscription-filter')\
.filter_pattern('[host, ident, authuser, date, request, status, bytes]')
end
end
但し、Awspec::Stub クラスの load メソッドで Stub を読み込んでいる点だと思います。
Type
Type では実装の Spec ファイルに記述された example の結果を返す処理を記述する部分です。
def has_log_stream?(stream_name)
ret = find_cloudwatch_logs_stream(@id).log_stream_name
return true if ret == stream_name
end
例えば、上記は example の以下の部分の呼び出しに対応します。
describe cloudwatch_logs('my-cloudwatch-logs-group') do
it { should have_log_stream('my-cloudwatch-logs-stream') }
end
have_log_stream
がマッチャとなり、呼び出されるメソッドは has_log_stream?
となります。
Helper
Helper モジュールには AWS SDK クライアントの定義、各 AWS リソースを取得する為のメソッドを定義したりします。
/lib/awspec/helper/finder.rb
- finder モジュールにて CloudWatch Logs 用のモジュールを include する
- CloudWatch Logs Client を定義する
lib/awspec/helper/finder/cloudwatch_logs.rb
- AWS SDK を利用して各種リソースを取得する為のコードを定義する
- 例えば、以下のように CloudWatch Logs の Log group を取得するメソッドを書いたりする
def find_cloudwatch_logs_group(id)
cloudwatch_logs_client.describe_log_groups({ log_group_name_prefix: id }).log_groups.last
end
ちょっとマッチャ
lib/awspec/matcher/have_subscription_filter.rb
今回、一番テストしたかったのが、CloudWatch Subscription filter のフィルタパターンだったんですが、フィルタパターンをテストするにはマッチャを独自に定義する必要たありました。独自のマッチャを定義するには、以下のように RSpec::Matchers.define
を呼び出す必要がありました。
RSpec::Matchers.define :have_subscription_filter do |filter_name|
match do |log_group_name|
log_group_name.has_subscription_filter?(filter_name, @pattern)
end
chain :filter_pattern do |pattern|
@pattern = pattern
end
end
また、lib/awspec/matcher.rb で上記の have_subscription_filter.rb を include してあげる必要があります。
require 'awspec/matcher/have_subscription_filter'
そして、以下のように example を記述してフィルタパターンをテストすることが出来ました。
describe cloudwatch_logs('my-cloudwatch-logs-group') do
it do
should have_subscription_filter('my-cloudwatch-logs-subscription-filter')\
.filter_pattern('[host, ident, authuser, date, request, status, bytes]')
end
end
Document
ちゃんとドキュメントも追加しましょう。
今回はリソースタイプの追加になりますので、doc/_resource_types/cloudwatch_logs.md を以下のように追加しました。
合わせて、自動生成されていた lib/awspec/generator/doc/type/cloudwatch_logs.rb もチェックします。
$ cat lib/awspec/generator/doc/type/cloudwatch_logs.rb
module Awspec::Generator
module Doc
module Type
class CloudwatchLogs < Base
def initialize
super
@type_name = 'CloudwatchLogs'
@type = Awspec::Type::CloudwatchLogs.new('my-cloudwatch-logs-group')
@ret = @type.resource_via_client
@matchers = []
@ignore_matchers = []
@describes = []
end
end
end
end
end
尚、Awspec::Type::CloudwatchLogs.new('my-cloudwatch-logs-group')
の引数 my-cloudwatch-logs-group
はドキュメントの引数と合わせておく必要があります。
最後に以下のようにドキュメントを書き出します。
$ bundle exec bin/toolbox docgen > doc/resource_types.md
rubocop
ここまで来るとあとはプルリクエストを…と思った貴方、もうひと頑張りが必要です。rubocop という国家権力(国家は余計)と戦う必要があります。
rubocop とはコーディングルールに準拠しているかをチェックしてくれるツールで、この警察権力のチェックを事前に行っておくことで、プルリクエスト後の Travis CI でのテストも乗り切ることが出来るはずです。
bundle exec rake spec:rubocop
以下のように出力されれば無事に釈放です。
bash-3.2$ bundle exec rake spec:rubocop
Running RuboCop...
Inspecting 346 files
..........................................................................................................................................................................................................................................................................................................................................................
346 files inspected, no offenses detected
ということで
ひとまずテストを動かしてみると…
bash-3.2$ bundle exec rake spec:cloudwatch_logs
(略)
cloudwatch_logs 'my-cloudwatch-logs-group'
should exist
should have log stream "my-cloudwatch-logs-stream"
should have metric filter "my-cloudwatch-logs-metric-filter"
should have subscription filter "my-cloudwatch-logs-subscription-filter"
retention_in_days
should eq 365
Finished in 0.0841 seconds (files took 1.45 seconds to load)
5 examples, 0 failures
おお、Stub のレスポンスとなりますが、テストが通りました。
わーい。
拡張し易い awspec
これまで書いてきましたが、awspec は自分のような Ruby 初心者でも比較的簡単に拡張することが出来ました。これは awspec 実装の際に参考にされたとされる Serverspec でも同様のことが言えると思います。本当に作者の方には感謝の言葉しかありません。有難うございます!
ということで、福岡生まれ、世界育ちの awspec を頑張っていこうと思います。