awspec generateでどれくらいテストを生成できるか試してみる

[awspec](https://github.com/k1LoW/awspec)はAWSのリソース構成のテスティングフレームワークですが、
実は便利コマンドとして、`awspec generate [rerouce type]`というコマンド群があります。

これはawspec用のテストを現在のAWSリソース構成から生成するコマンドです。

v0.25.3時点で以下のようなコマンドがあります。

```sh
$ awspec generate
Commands:
awspec generate cloudwatch_alarm                    # Generate cloudwatch_alarm spec
awspec generate directconnect                       # Generate directconnect spec
awspec generate ebs                                 # Generate attached ebs spec
awspec generate ec2 [vpc_id]                        # Generate ec2 spec from VPC ID (or VPC "Name" tag)
awspec generate elb [vpc_id]                        # Generate elb spec from VPC ID (or VPC "Name" tag)
awspec generate help [COMMAND]                      # Describe subcommands or one specific subcommand
awspec generate iam_policy                          # Generate attached iam_policy spec
awspec generate network_acl [vpc_id]                # Generate network_acl spec from VPC ID (or VPC "Name" tag)
awspec generate rds [vpc_id]                        # Generate rds spec from VPC ID (or VPC "Name" tag)
awspec generate route53_hosted_zone [example.com.]  # Generate route53_hosted_zone spec from Domain name
awspec generate route_table [vpc_id]                # Generate route_table spec from VPC ID (or VPC "Name" tag)
awspec generate s3_bucket [backet_name]             # Generate s3_bucket spec from S3 bucket name. if NO args, Generate all.
awspec generate security_group [vpc_id]             # Generate security_group spec from VPC ID (or VPC "Name" tag)
awspec generate subnet [vpc_id]                     # Generate subnet spec from VPC ID (or VPC "Name" tag)
awspec generate vpc [vpc_id]                        # Generate vpc spec from VPC ID (or VPC "Name" tag)

Options:
[--profile=PROFILE]
```

今回このコマンド群でどれくらいのテストを生成できるか試してみたいと思います。

## 対象のVPC構成

今回対象とするのは、シンプルなEC2とRDSのVPC構成です。

![Untitled (10).png](https://qiita-image-store.s3.amazonaws.com/0/808/221b1528-def7-0155-b968-32f4088a53cf.png)

対象VPC構成のTerraformのtfファイルはこちら。

https://github.com/k1LoW/playground/tree/master/terraform/ec2-and-rds

VPCのtag:Nameは`my-vpc`です。

## テストファイルの生成

それでは早速テストファイルを生成します。すぐに `rake spec` が実行できるように初期設定も同時に行います。

```sh
$ mkdir advent2015aws
$ cd advent2015aws/
$ awspec init
 + spec/
 + spec/spec_helper.rb
 + Rakefile
 + spec/.gitignore
 + .rspec
$ echo "require 'spec_helper'" > spec/ec2_and_rds_spec.rb
$ awspec generate vpc my-vpc >> spec/ec2_and_rds_spec.rb
$ awspec generate ec2 my-vpc >> spec/ec2_and_rds_spec.rb
$ awspec generate elb my-vpc >> spec/ec2_and_rds_spec.rb
$ awspec generate network_acl my-vpc >> spec/ec2_and_rds_spec.rb
$ awspec generate rds my-vpc >> spec/ec2_and_rds_spec.rb
$ awspec generate route_table my-vpc >> spec/ec2_and_rds_spec.rb
$ awspec generate security_group my-vpc >> spec/ec2_and_rds_spec.rb
$ awspec generate subnet my-vpc >> spec/ec2_and_rds_spec.rb
```

たったこれだけです。

これだけで生成されたテストケース `ec2_and_rds_spec.rb` はこちら。

```ruby
require 'spec_helper'
describe vpc('my-vpc') do
  it { should exist }
  it { should be_available }
  its(:vpc_id) { should eq 'vpc-12a34567' }
  its(:cidr_block) { should eq '10.1.0.0/16' }
  it { should have_route_table('my-route-table-private') }
  it { should have_route_table('rtb-98b76543') }
  it { should have_route_table('my-route-table-public') }
  it { should have_network_acl('acl-54321a98') }
end
describe ec2('my-instance') do
  it { should exist }
  it { should be_running }
  its(:instance_id) { should eq 'i-66b873e9' }
  its(:image_id) { should eq 'ami-b80b6db8' }
  its(:private_dns_name) { should eq 'ip-10-1-1-251.ap-northeast-1.compute.internal' }
  its(:public_dns_name) { should eq '' }
  its(:instance_type) { should eq 't2.micro' }
  its(:private_ip_address) { should eq '10.1.1.251' }
  its(:public_ip_address) { should eq '52.68.207.91' }
  it { should have_security_group('my-web-sg') }
  it { should belong_to_vpc('my-vpc') }
  it { should belong_to_subnet('my-subnet-public-c') }
  it { should have_eip('52.68.207.91') }
  it { should have_ebs('vol-71d442b8') }
  it { should have_ebs('vol-64d442ad') }
end

describe network_acl('acl-54321a98') do
  it { should exist }
  it { should belong_to_vpc('my-vpc') }
  it { should have_subnet('my-subnet-private-rds-a') }
  it { should have_subnet('my-subnet-private-rds-c') }
  it { should have_subnet('my-subnet-public-c') }
  its(:outbound) { should be_allowed.protocol('ALL').source('0.0.0.0/0').rule_number(100) }
  its(:outbound) { should be_denied.protocol('ALL').source('0.0.0.0/0').rule_number('*') }
  its(:inbound) { should be_allowed.protocol('ALL').source('0.0.0.0/0').rule_number(100) }
  its(:inbound) { should be_denied.protocol('ALL').source('0.0.0.0/0').rule_number('*') }
  its(:inbound_entries_count) { should eq 2 }
  its(:outbound_entries_count) { should eq 2 }
end
describe rds('my-rds') do
  it { should exist }
  it { should be_available }
  its(:db_instance_identifier) { should eq 'my-rds' }
  its(:db_instance_class) { should eq 'db.t2.micro' }
  its(:multi_az) { should eq false }
  its(:availability_zone) { should eq 'ap-northeast-1a' }
  it { should have_security_group('my-db-sg') }
  it { should belong_to_vpc('my-vpc') }
  it { should belong_to_db_subnet_group('my-rds-subnet-group') }
  it { should have_db_parameter_group('my-rds-pg') }
  it { should have_option_group('default:mysql-5-6') }
end
describe route_table('my-route-table-private') do
  it { should exist }
  it { should belong_to_vpc('my-vpc') }
  it { should have_route('10.1.0.0/16').target(gateway: 'local') }
  it { should have_subnet('my-subnet-private-rds-c') }
  it { should have_subnet('my-subnet-private-rds-a') }
end

describe route_table('rtb-98b76543') do
  it { should exist }
  it { should belong_to_vpc('my-vpc') }
  it { should have_route('10.1.0.0/16').target(gateway: 'local') }
end

describe route_table('my-route-table-public') do
  it { should exist }
  it { should belong_to_vpc('my-vpc') }
  it { should have_route('10.1.0.0/16').target(gateway: 'local') }
  it { should have_route('0.0.0.0/0').target(gateway: 'igw-2015a123') }
  it { should have_subnet('my-subnet-public-c') }
end
describe security_group('default') do
  it { should exist }
  its(:group_id) { should eq 'sg-1ab2cd34' }
  its(:group_name) { should eq 'default' }
  its(:inbound) { should be_opened }
  its(:outbound) { should be_opened }
  its(:inbound_rule_count) { should eq 1 }
  its(:outbound_rule_count) { should eq 1 }
  its(:inbound_permissions_count) { should eq 1 }
  its(:outbound_permissions_count) { should eq 1 }
  it { should belong_to_vpc('my-vpc') }
end
describe security_group('my-web-sg') do
  it { should exist }
  its(:group_id) { should eq 'sg-9ed8cba' }
  its(:group_name) { should eq 'my-web-sg' }
  its(:inbound) { should be_opened(22).protocol('tcp').for('0.0.0.0/0') }
  its(:outbound) { should be_opened }
  its(:inbound_rule_count) { should eq 1 }
  its(:outbound_rule_count) { should eq 1 }
  its(:inbound_permissions_count) { should eq 1 }
  its(:outbound_permissions_count) { should eq 1 }
  it { should belong_to_vpc('my-vpc') }
end

describe security_group('my-db-sg') do
  it { should exist }
  its(:group_id) { should eq 'sg-12b3bb4b' }
  its(:group_name) { should eq 'my-db-sg' }
  its(:inbound) { should be_opened(3306).protocol('tcp').for('sg-9ed8cba') }
  its(:outbound) { should be_opened }
  its(:inbound_rule_count) { should eq 1 }
  its(:outbound_rule_count) { should eq 1 }
  its(:inbound_permissions_count) { should eq 1 }
  its(:outbound_permissions_count) { should eq 1 }
  it { should belong_to_vpc('my-vpc') }
end
describe subnet('my-subnet-public-c') do
  it { should exist }
  it { should be_available }
  it { should belong_to_vpc('my-vpc') }
  its(:subnet_id) { should eq 'subnet-999999c9' }
  its(:cidr_block) { should eq '10.1.1.0/24' }
end

describe subnet('my-subnet-private-rds-a') do
  it { should exist }
  it { should be_available }
  it { should belong_to_vpc('my-vpc') }
  its(:subnet_id) { should eq 'subnet-a1111111' }
  its(:cidr_block) { should eq '10.1.2.0/24' }
end

describe subnet('my-subnet-private-rds-c') do
  it { should exist }
  it { should be_available }
  it { should belong_to_vpc('my-vpc') }
  its(:subnet_id) { should eq 'subnet-989898cc' }
  its(:cidr_block) { should eq '10.1.3.0/24' }
end
```

最後にテストを実行してみます。

```
$ rake spec

...

Finished in 3.64 seconds (files took 6.75 seconds to load)
103 examples, 0 failures
```

GREEN! :white_check_mark: 

## まとめ

このように、`awspec generate`も実用的になってきています。

例えば、CloudFormationやTerraformようなオーケストレーションツールなどで管理されていない、AWSリソース構成のテストケース作成に有効に活用できるのではないかと思います。