手動テスト・QAの自動化を行うための、インテグレーションテスト(結合テスト)入門


この記事の対象者

  • 主に小〜中規模Webアプリケーションの開発に携わっている
  • テストを書くことがある・もしくはこれから書いてみようと思っている
  • 手動テスト(QA)の負担を減らしたい
  • インテグレーションテスト導入のメリットを、コードを交えて3分で理解したい

本記事の内容

  • インテグレーションテストとは
  • ユニットテストだけではダメなのか?
  • インテグレーションテスト導入のメリット
  • インテグレーションテスト導入の方法
  • インテグレーションテストを実際に導入した感想

インテグレーションテストとは

一般には、ユニットテストが完了したプログラムを複数組み合わせて行うテストを指す。
ユニットテストを組み合わせることで、様々なパターンにおいて動作やふるまいが正常であるかをテストできる。
2015年4月のGoogleによるTesting Blogによると、ユニットテスト・インテグレーションテスト・End to Endテストの割合はそれぞれ7:2:1となるべきである、とされている(Google Testing Pyramidより)。

ユニットテストだけではダメなのか?

ユニットテストの定義

あえて一言でまとめてみると、
ユニットテストとは、テスト対象(主に個々のメソッド単位)にある条件のもと入力を与えると、期待通りの出力が得られるかを試すものである。
多くのWebエンジニアが”テスト”と呼んでいるものは、ユニットテストである。

ユニットテストのメリット

主に以下の点が挙げられる:
* コードがシンプルで、誰でも簡単に書ける
* コードが短く、実行時間が早い
* つまり、コストパフォーマンスがよい

ユニットテストだけではダメなのか?

個人的に、 ユニットテストのみで動くアプリケーションが決してダメだとは思わない。
事実、勉強会などでWebエンジニアに話しを伺うと、残念ながら少なくない数のアプリケーションにおいてテストが書かれていない。
また、テストが書かれているアプリケーションにおいても、その多くはユニットテストのみで動いている。
前述のとおりユニットテストはコストパフォーマンスがいいため、厳しいビジネス要件なども加味するとユニットテストがあるアプリケーション(かつテストカバレッジを可視化しているアプリケーション)を非難することなど(少なくとも自分には)できない。

インテグレーションテスト導入のメリット

インテグレーションテストを導入することによるメリットは、 テスト対象のユニットが複数組み合わされたときに正しく機能するかを確かめられる ことにある。

たとえば、実際のアプリケーションにおいては、
* ユニットテストだけでは不安がある
* テスト対象のユニットが複数組み合わされたときに正しく機能するかを確かめたい
* 毎度決まったテスト項目を手動テストしているが、これを自動化してリリース時の負担を減らしたい
* javascriptなども含めて、バックエンドからフロントエンドまで一気通貫して、きちんとアプリケーションが動作しているか見たい
などのような、
ユニットテストは書いてあるが、たとえば上記項目のような不安・不十分さを感じてインテグレーションテストの導入を検討するケースが多いと個人的には感じる。

つまり、テスト対象を組み合わせてテストするインテグレーションテストによって、上記の要望を達成することができる。

インテグレーションテスト導入方法

実際のプロジェクトにてインテグレーションテストを導入した方法について紹介する

シナリオ作成

  1. 既存のコードがある場合、ユーザが主にどのように動くか、主要なシナリオを確認する
  2. 手動QAを観察し、どのようなシナリオでQAを行っているか確認する
  3. 上記2つからインテグレーションテストのテストケースとなるシナリオを作成し、シナリオリストを作成する。

シナリオ実装

  1. シナリオリストから機能ごと、もしくはふるまいごとにグループ化してissueを作成する
  2. issueごとに実装を行い、実装が完了したシナリオはリストに必ずその旨記載する。

実装コード

本記事ではテストフレームワークとしてRspecを用いる。
RspecはRuby on Railsのテストライブラリであり、BDDのtoolである。
また、ユーザインタラクションをテストするため、Capybaraライブラリを用いる。

たとえば、ECアプリケーションにてユーザが商品を購入する、というシナリオであれば:

spec/features/user_buys_a_product_spec.rb
feature 'User buys a product' do
  scenario 'Can complete all the process', js: true do
    user_tries_to_buy_a_product
    user_can_choose_amount_of_units
    user_can_select_the_address
    user_can_confirm_the_order
    thank_you_page_is_showen
    user_pressing_back_button_is_redirected_to_cart_page
  end
end

上記のようにシナリオが描ける。
シナリオにおけるふるまいはそれぞれメソッドに切り出し、

spec/features/user_buys_a_product_spec.rb
feature 'User buys a product' do
  ...  

  def user_tries_to_buy_a_product
    create_normal_product
    load_page_with_valid_user product_path(@product)
    stub_user_has_one_credit_card
    find('.c-btn--submit').click
  end
end

上記のようにメソッド内でデータ準備・スタブ・ページロード・ユーザ動作を記述する。
以上のようにシナリオからテストを設計していくことで、

feature 'Feature name' do
  scenario 'Scenario' do
    user_behavior1
    user_behavior2    
    ...
  end

  def user_behavior1
    set_data
    user_behavior1_1
  end

  def set_data
    create_some_data
  end 

  ...
end

上記のようにシナリオに従ってメソッドを定義していくだけでインテグレーションテストがきれいに書ける。

実際にインテグレーションを導入してみた感想

よかった点

・ユニットテストと組み合わせ、様々なプログラムのふるまいをテストできるようになった。
・毎度ルーチンとして行っていた手動テストを自動化でき、リリース時の負担が減った

改善点

・インテグレーションテストは実装・実行に時間がかかる。
・インテグレーションテストで失敗したケースを見ると、その多くは”インテグレーションテストでなくても見つけられるバグ”が多かった。
・上記2点より、インテグレーションテストより先に実行される、スモークテストを実装した。スモークテスト・ユニットテスト・インテグレーションテストの組み合わせが小〜中規模webアプリケーションには有効ではないかと個人的に感じた。

まとめ

・ユニットテストも十分に効果的だが、”それだけだと不安”。
・インテグレーションテストは手動QAを効果的に削減できる。
・ぜひインテグレーションテストの導入を検討してみてはどうでしょうか。

※本記事は、LifeTech – meetup for engineer #01にて講演させて頂きました内容から一部抜粋しました。
※広く定義される各テスト手法の定義から少し外れるところもあります。厳密な定義などについてはWikiなどご参照ください。