ようへいの日々精進XP

よかろうもん

moto ✕ Rspec で Ruby で書いたライブラリをテストするばい(moto チュートリアル 弐)

モト

inokara.hateblo.jp

moto という AWS SDK のレスポンスを模倣する Python ライブラリについて引き続きです。

Stand-alone Server Mode

moto には Stand-alone Server Mode という Flask で実装された Mock サーバーが提供されていて、HTTP サーバーなので Python に限らず、他の言語ライブラリからも利用出来るのがスーパーメリットだと思いました。

github.com

実際に GitHub リポジトリにも https://github.com/spulec/moto/tree/master/other_langs というディレクトリに RubyJava から利用するサンプルが提供されています。

example

Install moto server

$ cat requirements.txt
moto[server]

$ pip install -r requirements.txt

Start moto server

まずは Help

$ moto_server --help
usage: moto_server [-h] [-H HOST] [-p PORT] [service]

positional arguments:
  service

optional arguments:
  -h, --help            show this help message and exit
  -H HOST, --host HOST  Which host to bind
  -p PORT, --port PORT  Port number to use for connection

EC2 用サーバーを起動

$ moto_server ec2
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Ruby Script

以下のようにインスタンス一覧を取得するだけのライブラリをサンプルとして利用します。

require 'aws-sdk'

class MyEc2

  def ec2
     @ec2 ||= Aws::EC2::Client.new(profile: 'mock_profile',
                                   region: 'us-west-2',
                                   endpoint: 'http://127.0.0.1:5000')
  end

  def list_ec2_instances
    instance_ids = []
    ec2.describe_instances.reservations.each do |res|
      res.instances.each do |instance|
        instance_ids << instance.instance_id
      end
    end
    instance_ids
  end

  def main
    list_ec2_instances.each do |instance|
      instance
    end
  end
end

# myec2 = MyEc2.new
# p myec2.main

moto_server の URL とポートを Aws::EC2::Clientインスタンスを生成する際の引数として endpoint を指定するだけで moto_server が利用出来ます。

最後の 2 行のコメントアウトを外して実行すると以下のようなレスポンスが得られます。

$ bundle exec ruby my_ec2.rb
[]

moto_server を起動したコンソールでは以下のようにログが出力されていることを確認出来るかと思います。

$ moto_server ec2
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [21/Apr/2017 00:37:27] "POST / HTTP/1.1" 200 -

Rspec で moto を利用してテストを行う

上記の Ruby スクリプト(ライブラリ)を moto と Rspec でテストしてみたいと思いますので、以下のようなテストを書きます。

$LOAD_PATH.push('./')
require "spec_helper"
require "my_ec2"
require "pty"

describe 'MyEc2' do
  let(:myec2) { MyEc2.new }
  before(:all) do
    @output, @input = PTY.spawn("moto_server ec2")
    client = Aws::EC2::Client.new(region: 'us-west-2', endpoint: 'http://127.0.0.1:5000', profile: 'mock_profile')
    res = client.run_instances({ image_id: 'ami-20d1c544', min_count: 3, max_count: 3 })
    @instance_ids = res.instances.map { |instance| instance.instance_id }
  end

  it "#ec2" do
    res = myec2.ec2
    expect(res).to be_an_instance_of(Aws::EC2::Client)
  end

  it '#list_ec2_instances' do
    res = myec2.list_ec2_instances
    expect(res).to eq @instance_ids
  end

  it '#main' do
    res = myec2.main
    expect(res).to eq @instance_ids
  end

end

ポイントは before 句で moto_server にアクセスして EC2 インスタンス 3 台を Run Instance している部分です。

実際にテストを走らせてみると…

moto_server が実行出来る環境にて、以下のように rspec を実行してみます。

$ bundle exec rspec spec

MyEc2
  #ec2
  #list_ec2_instances
  #main

Finished in 1.47 seconds (files took 0.16527 seconds to load)
3 examples, 0 failures

うまくテストが通ったようです。

以上

Rspec の書き方がだいぶん怪しいですが、moto_server を使うことでリアルな AWS リソースにアクセスすることなく、スクリプトやライブラリのテストやデバッグが言語に関係無く出来るといのは嬉しい限りです。

有難うございました。

おまけ

moto については 4/22(土)に厳かに執り行われる「JAWS-UG福岡:Reboot#4、荒木さんとAWSの話をしてみたり JAWS DAYS 参加者から話をきいてみたりしよう」にて語らせて頂く予定でござります。

speakerdeck.com