コンポーネント間結合テスト自動化目的でのTurnipの利用検討

# はじめに

そこそこの規模のシステムのコンポーネント間のテストにTurnipがどの程度適合するか試しています。各コンポーネント間でのデータはHTTP APIコールやファイルなどでやりとりするようなシステムが前提です。筆者はRubyについては初心者に毛が生えない程度ですがその他よさそうなツールがないので見よう見まねで使おうという算段です。


## 目的
* (1)テストケース作成→(2)テスト準備→(3)テスト実行→(4)テスト結果記入　のステップを極力自動化したい
    * (1)(4)のためにExcelでテスト仕様書を書くのをやめたい
        * (2)のアウトプットが(3)で直接実行可能なように記述できる
        * (2)のアウトプットが可読性が高く(1)の代わりになる
        * (3)のアウトプットがそのまま(4)の代替になる

## 利用ツールの検討

上記目的を満たすものとして、受入テストに使うような、要求仕様にたいしてブラックボックステストをするようなツールの利用について検討をしました。

### 各ツールについての検討
* [ディシプリンド・アジャイル・デリバリー　エンタープライズ・アジャイル実践ガイド(2013)](https://goo.gl/OxKXMT)
    * Fitnesse ... Java製、管理UI、Wikiでテストケース記述、直近１年の日本語情報少ない
    * Twist ... IDE、直近１年の日本語情報がほぼない
    * JBehave ...  Java製、大変そう https://goo.gl/rnmyMZ
    * Concordion ... Java製、HTMLでのアサーション https://goo.gl/PWdeu4
    * Cucumber ... Ruby製、日本語情報は他にくらべれば多い

ということで日本語でシナリオが記述できて目的とも合致しやすそうなCucumberの利用を検討しましたが、以下の記事を読んで、さらにとっつきやすそうなTurnipを利用することにしました。

* [エンドツーエンドテストの自動化は Cucumber から Turnip へ](http://magazine.rubyist.net/?0042-FromCucumberToTurnip)
* [Ruby製アプリじゃなくてもTurnipで自動受け入れテストがしたい](http://orangain.hatenablog.com/entry/turnip)

# Turnip のインストールとテスト

こちらを参考にインストールを進めます。
[Turnip について (1) / まずは動かす](http://gongo.hatenablog.com/entry/2013/05/29/223218)


## 環境

- Amazon Linux AMI release 2016.09 
    - kernel: 4.4.41-36.55.amzn1.x86_64

## 手順 

### rubyのバージョン確認

```
$ ruby -v
ruby 2.0.0p648 (2015-12-16) [x86_64-linux]
```

### 事前準備

#### Gemfile
```
$ mkdir turnip_example
$ cd turnip_example/
$ mkdir spec
$ cat > Gemfile
```

```ruby:Gemfile
source 'https://rubygems.org'

gem 'turnip'
```
#### .rspec
```
$ cat > .rspec
```

```ruby:.rspec
-r turnip/rspec
--color
--format documentation
```

#### spec_helper.rb
```
$ cat > spec/spec_helper.rb
```

```spec/spec_helper.rb
require 'rubygems'
require 'bundler/setup'

Dir.glob("spec/steps/**/*steps.rb") { |f| load f, true }
```

[Bundle Exec Rspec spec/*/*.featureが失敗する件](http://rice-american.github.io/blog/2014/02/08/jenkins-rspec/)
のよう .rspec に turnipだけしか記載しないと実行に失敗しました。
rspecのフォーマットについては [RSpecを斬る > フォーマット指定](http://samurai.ataglance.jp/rspec_format/) を参考にしました。

#### bundleがないのでインストール

```
$ gem install bundler
Fetching: bundler-1.14.3.gem (100%)
Bundler and RubyGems.org are free for anyone to use, but maintaining them costs more than $25,000 USD every month. Help us cover those costs so that we can keep the gem ecosystem free for everyone: https://ruby.to/support-bundler
Successfully installed bundler-1.14.3
Parsing documentation for bundler-1.14.3
Installing ri documentation for bundler-1.14.3
Done installing documentation for bundler after 5 seconds
1 gem installed
```

ただインストールしただけでは動かないので以下を参考に追加インストール

[EC2でBundle Installすると、io/consoleエラーになる](http://qiita.com/saicologic/items/6c9fdc728fbd6f004e4e)
[Amazon Linuxでbundle installのエラーに対処する](http://dev.classmethod.jp/cloud/aws/amazon-linux-bundler-error/)

```
$ sudo yum install -y ruby-devel
$ gem install io-console
```

### ようやくturnipのインストールができます

```
$ bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/............
Fetching version metadata from https://rubygems.org/..
Resolving dependencies...
Rubygems 2.0.14.1 is not threadsafe, so your gems will be installed one at a time. Upgrade to Rubygems 2.1.0 or higher to enable parallel gem installation.
Installing diff-lcs 1.3
Installing multi_json 1.12.1
Installing rspec-support 3.5.0
Using bundler 1.14.3
Installing gherkin 2.12.2 with native extensions
Installing rspec-core 3.5.4
Installing rspec-expectations 3.5.0
Installing rspec-mocks 3.5.0
Installing rspec 3.5.0
Installing turnip 2.1.1
Bundle complete! 1 Gemfile dependency, 10 gems now installed.
Bundled gems are installed into ./vendor/bundle.
```
このあとbundle exec rspecするとインストールされたmulti_jsonが毎回WARNINGを吐いてうっとおしいのでvendor/bundle以下のファイルを直接コメントアウトしました。
[MultiJson is using the default adapter (ok_json). We recommend loading a different JSON library to improve performance.](http://selenium.10932.n7.nabble.com/Re-WARNING-MultiJson-is-using-the-default-adapter-ok-json-We-recommend-loading-a-different-JSON-libr-td21992.html)


### featuresファイルの作成

```
$ mkdir spec/features
$ cat > spec/features/aggregation.feature
```

テストとして、以下のようなウェブサーバにユーザからリクエストが飛んで、そのリクエストのログが集計用の別コンポーネントで正常に集計されているかどうかを確認する機能のシナリオを作成しました。

```aggregation.feature
Feature: リクエスト集計

  Background:
   Given 各サービスが正常動作

  Scenario: １リクエストが正常に集計される
    Given １ユーザが１リクエスト
    When 集計処理が完了
    Then 集計結果に反映

  Scenario: 複数リクエストが正常に集計される
    Given ３ユーザが各々５リクエスト
    When 集計処理が完了
    Then 集計結果に反映

  Scenario: 不正なリクエストは集計から除外される
    Given 不正なリクエスト
    When 集計処理が完了
    Then 集計結果に反映されない
```

```
$ mkdir spec/steps
$ cat > spec/steps/aggregation_steps.rb
```

各ステップは以下のように定義しました。

```aggregation_steps.rb
# coding: utf-8

step "各サービスが正常動作" do
  # ウェブサービスの正常動作確認
  # 集計サービスの正常動作確認
end

step "１ユーザが１リクエスト" do
  # 期待する結果の差分は１リクエスト
  @expected_diff = 1
  # 現在のリクエスト数を取得する処理
  @original_count = 1323
  # サーバへのHTTPリクエスト実施
end

step "３ユーザが各々５リクエスト" do
  # 期待する結果の差分は１リクエスト
  @expected_diff = 15
  # 現在のリクエスト数を取得する処理
  @original_count = 1309
  # サーバへのHTTPリクエスト実施
end

step "不正なリクエスト" do
  # 期待する結果の差分は０リクエスト
  @expected_diff = 0
  # 現在のリクエスト数を取得する処理
  @original_count = 1324
  # サーバへのHTTPリクエスト実施
end

step "集計処理が完了" do
  # 集計処理完了待ち
  # 集計処理完了後結果取得
  @current_count = 1324
end

step "集計結果に反映" do
  @actual_diff = @current_count - @original_count
  expect(@actual_diff).to be == @expected_diff
end

step "集計結果に反映されない" do
  @actual_diff = @current_count - @original_count
  expect(@actual_diff).to be == 0
end
```

### 実行します

```
$ bundle exec rspec

リクエスト集計
  １リクエストが正常に集計される
    Given 各サービスが正常動作 -> Given １ユーザが１リクエスト -> When 集計処理が完了 -> Then 集計結果に反映
  複数リクエストが正常に集計される
    Given 各サービスが正常動作 -> Given ３ユーザが各々５リクエスト -> When 集計処理が完了 -> Then 集計結果に反映
  不正なリクエストは集計から除外される
    Given 各サービスが正常動作 -> Given 不正なリクエスト -> When 集計処理が完了 -> Then 集計結果に反映されない

Finished in 0.0047 seconds (files took 0.22233 seconds to load)
3 examples, 0 failures
```

#### 意図的にエラーを起こすようにした例（長い）

"集計処理が完了"の@current_count値をいじって意図的にエラーを出すようにします。

```
$ bundle exec rspec

リクエスト集計
  １リクエストが正常に集計される
    Given 各サービスが正常動作 -> Given １ユーザが１リクエスト -> When 集計処理が完了 -> Then 集計結果に反映 (FAILED - 1)
  複数リクエストが正常に集計される
    Given 各サービスが正常動作 -> Given ３ユーザが各々５リクエスト -> When 集計処理が完了 -> Then 集計結果に反映 (FAILED - 2)
  不正なリクエストは集計から除外される
    Given 各サービスが正常動作 -> Given 不正なリクエスト -> When 集計処理が完了 -> Then 集計結果に反映されない (FAILED - 3)

Failures:

  1) リクエスト集計 １リクエストが正常に集計される Given 各サービスが正常動作 -> Given １ユーザが１リクエスト -> When 集計処理が完了 -> Then 集計結果に反映
     Failure/Error: expect(@actual_diff).to be == @expected_diff

       expected: == 1
            got:    -2
     # spec/steps/aggregation_steps.rb:40:in `block in <top (required)>'
     # ./vendor/bundle/ruby/2.0/gems/turnip-2.1.1/lib/turnip/execute.rb:25:in `step'
     # ./spec/features/aggregation.feature:9:in `run_step'
     # ./vendor/bundle/ruby/2.0/gems/turnip-2.1.1/lib/turnip/rspec.rb:44:in `instance_eval'
     # ./vendor/bundle/ruby/2.0/gems/turnip-2.1.1/lib/turnip/rspec.rb:44:in `run_step'
     # ./spec/features/aggregation.feature:8:in `block (4 levels) in run_feature'
     # ./spec/features/aggregation.feature:7:in `each'
     # ./spec/features/aggregation.feature:7:in `block (3 levels) in run_feature'
     # ./spec/features/aggregation.feature:9:in `集計結果に反映'

  2) リクエスト集計 複数リクエストが正常に集計される Given 各サービスが正常動作 -> Given ３ユーザが各々５リクエスト -> When 集計処理が完了 -> Then 集計結果に反映
     Failure/Error: expect(@actual_diff).to be == @expected_diff

       expected: == 15
            got:    12
     # spec/steps/aggregation_steps.rb:40:in `block in <top (required)>'
     # ./vendor/bundle/ruby/2.0/gems/turnip-2.1.1/lib/turnip/execute.rb:25:in `step'
     # ./spec/features/aggregation.feature:14:in `run_step'
     # ./vendor/bundle/ruby/2.0/gems/turnip-2.1.1/lib/turnip/rspec.rb:44:in `instance_eval'
     # ./vendor/bundle/ruby/2.0/gems/turnip-2.1.1/lib/turnip/rspec.rb:44:in `run_step'
     # ./spec/features/aggregation.feature:13:in `block (4 levels) in run_feature'
     # ./spec/features/aggregation.feature:12:in `each'
     # ./spec/features/aggregation.feature:12:in `block (3 levels) in run_feature'
     # ./spec/features/aggregation.feature:14:in `集計結果に反映'

  3) リクエスト集計 不正なリクエストは集計から除外される Given 各サービスが正常動作 -> Given 不正なリクエスト -> When 集計処理が完了 -> Then 集計結果に反映されない
     Failure/Error: expect(@actual_diff).to be == 0

       expected: == 0
            got:    -3
     # spec/steps/aggregation_steps.rb:45:in `block in <top (required)>'
     # ./vendor/bundle/ruby/2.0/gems/turnip-2.1.1/lib/turnip/execute.rb:25:in `step'
     # ./spec/features/aggregation.feature:19:in `run_step'
     # ./vendor/bundle/ruby/2.0/gems/turnip-2.1.1/lib/turnip/rspec.rb:44:in `instance_eval'
     # ./vendor/bundle/ruby/2.0/gems/turnip-2.1.1/lib/turnip/rspec.rb:44:in `run_step'
     # ./spec/features/aggregation.feature:18:in `block (4 levels) in run_feature'
     # ./spec/features/aggregation.feature:17:in `each'
     # ./spec/features/aggregation.feature:17:in `block (3 levels) in run_feature'
     # ./spec/features/aggregation.feature:19:in `集計結果に反映されない'

Finished in 0.01192 seconds (files took 0.18849 seconds to load)
3 examples, 3 failures

Failed examples:

rspec ./spec/features/aggregation.feature:6 # リクエスト集計 １リクエストが正常に集計される Given 各サービスが正常動作 -> Given １ユーザが１リクエスト -> When 集計処理が完了 -> Then 集計結果に反映
rspec ./spec/features/aggregation.feature:11 # リクエスト集計 複数リクエストが正常に集計される Given 各サービスが正常動作 -> Given ３ユーザが各々５リクエスト -> When 集計処理が完了 -> Then 集計結果に反映
rspec ./spec/features/aggregation.feature:16 # リクエスト集計 不正なリクエストは集計から除外される Given 各サービスが正常動作 -> Given 不正なリクエスト -> When 集計処理が完了 -> Then 集計結果に反映されない
```

# まとめ

作成中