テスト自動化について、調べてみた
はじめに
- 今までテストコードやテスト自動化をしたことがなく、今の会社に入ってテストコードを書き始めて半年ちょっと
- 言語はPHP,JavaScript(node)が中心
- テストコードを書く前にどういう経緯で自分がテストコードを書く理由やモチベーションが上げるために、テストコードやテストについて、自分が現在書いている実感も含めて少しまとめ。
テスト自動化までの歴史
-
1960年代
- テスト自動化をする手法は早くは1962年に考慮されている
- ソフトウェア危機
- 1960年代の終わり第1回NATOソフトウェア工学会議で主張された話、コンピュータの高性能化によって、ソフトウェア開発が複雑化が加速化していくと叫ばれた。
- このころから複雑化していくソフトウェア開発に対し、構造化プログラミングや自動テストなどのソフトウェア開発における方法論が議論が活発化し始めている。
-
1970年代
- ソフトウェアテストの技法 1976年
- ソフトウェアの複雑化が増して行く中でソフトウェアの品質を上げるためのテスト手法の検討や確立がされていった。
- オブジェクト指向プログラミング
- ソフトウェアの複雑性に対応するために、プログラミングを実際のオブジェクト(モノ)に例えて行うオブジェクト指向の考え方が流行り始めた
- ソフトウェアテストの技法 1976年
-
1980年代
- 人月の神話(銀の弾丸はない) 1988年
- ソフトウェア開発に関しての有名な論文。ソフトウェア開発において、どんな事象に対しても万能な方法はないという意味の
銀の弾丸はない
と言う有名な言葉を残した。他にも遅れているソフトウェアプロジェクトへの要員追加は、プロジェクトをさらに遅らせるだけである
というブルックスの法則の言葉も有名。
- ソフトウェア開発に関しての有名な論文。ソフトウェア開発において、どんな事象に対しても万能な方法はないという意味の
- 人月の神話(銀の弾丸はない) 1988年
-
1990年代
- Extreme Programing (通称XP)1999年 手法自体は1996年頃から
- small talkの開発者としても有名なkent beckが書いた開発手法の本。現代の自動テストの考え方の主要な考え方が載っている。(詳細は後述)
- Scrum
- アジャイル開発の手法の一つ。最近一番よく聞くイメージがある。
- 短い期間でスプリントを組み、スクラムマスター、プロダクトオーナー、チームが協力しあって、プロダクトを作成する手法。
- スクラムマスターはスクラムの導入の手助けを、プロダクトオーナーはプロダクトにビジネスの要件と責任、チームはプロダクトオーナーからのビジネス要求に応えるという形で互いが協力しあって開発を進めていく。
- XPと同時期に提唱された開発手法でテスト自動化についての細かい記述はないが、短い期間で開発するための手法として、基本的にテスト自動化が導入されていることが多い。
- Extreme Programing (通称XP)1999年 手法自体は1996年頃から
-
2000年代
- リーンスタートアップ
- 厳密にはソフトウェア開発手法ではなく、起業の方法論だが、スタートアップ企業でよく使われている開発手法。アジャイルの開発手法の一つ。実用最小限の製品 MVP(Minimum Viable Product)を作成し、仮説、検証を繰り返し、製品をよくしていく手法。繰り返しのタスクや方法論がXPやアジャイル手法に共通する部分が多くある。
- DevOps
- 開発 (Development) と運用 (Operations)を組み合わせたかばん語で、開発担当者と運用担当者が連携して協力する開発手法。運用、インフラと開発がシームレスに連携する開発手法。インフラの自動化などと一緒にテストの自動化の導入が提唱されている。
- リーンスタートアップ
ソフトウェアテストの種類
- テストの種類。テストフェーズとして下記の単語が使われる場合もあるが、ここではテストの種類として分類
会社によって色々な呼び方がある。
単体テスト
- 各moduleの機能を満たしているかを確認するテスト
- 通称 UT (Unit Test)
- 基本的にホワイトボックステストで行われる
結合テスト
- 各moduleを連結してテストする
- 通称 IT (Integration test)
- 機能テスト function testはこちらに類する
- 外部APIなどの外部接続するものテストを段階的に行う場合もある
- 基本的にブラックボックステスト、場合によってホワイトボックステスト的なイメージ
総合テスト
- 外部APIやブラウザなど全ての条件を結合するテスト
- 本番と同等の環境を用意してテストを行う。ここではプログラムが要件通りに動作するかを確認する
- UAT(ユーザー受け入れテスト)
- 基本的にブラックボックステストで行われる
テスト観点
- ソフトウェアテストを行う際に考えるべき、テストする観点は大きく分けて、
機能要件
と非機能要件
に分けられる。
機能要件
- クライアント要件を満たす部分
- 作成したコードがクライアントの要求する機能を満たしているのか、またそれが正しく要件通り動くのかを確認する
- テストコードで担保できる部分は基本的にはこちらの部分
非機能要件
- 仕様や機能以外の部分
- 情報セキュリティ
- パフォーマンス
- サーバー性能
- ネットワーク設計
- 障害時の復旧方法など
テストプロセス
- テスト分析
- テスト設計
テストコードの話
テストコードの種類
単体テスト
- ユニットテスト 通称 UT (Unit test)
- コードが開発者の意図と同一に動いていることを確認するテスト ≠ ビジネス要件を担保するテストではない
- メソッド毎に作成する。粒度は細かめ
- 他のmoduleやDB、外部apiに依存する部分はstub,mock化してテストする
- メリット
- テストコードを書くことで各moduleの機能範囲などを開発者がより深く理解することできる
- 粒度が細かく、バグが発生した箇所を細かく分析することができる
- メソッドの機能を表現するテストコードができて、開発者はテストコードを読んでそのメソッドの意図を後から読解することができる
- デメリット
- 網羅性を要求されるため、ロジック内容にによってテストコードを書くコストが大きくなる
- 密結合なコードだと、テストコードが書きづらく、疎結合なテストが書きやすいコードを要求されやすい
機能テスト
- ファンクショナルテスト
- 各module(外部APIやDB部分)が連結する部分も合わせてテストを行う。粒度は大きめ
- 複数のmoduleを合わせてプログラムが要件を満たしているかを含めてテストを行う
- メリット
- 単体ではわからない各moduleの結合条件によって引き起こされるバグを見つけることができる
- APIなどはプログラム単体のロジックではなく、要件レベルまで確認できる
- デメリット
- テスト粒度が大きいため、バグ発生箇所の特定が大変
- テスト内容がビジネスロジックや要件、外部との疎通IFも要求されるため、単純なコード内容の確認 + 要件への理解まで必要なので理解していないといけない範囲が大きくなる
UIテスト
- seleniumやappinumなどの実際のブラウザやアプリを動かすソフトウェアを使って行うテスト、一番粒度が大きい。
- メリット
- 全てを連結してテストをすることができるため、確認できる範囲は大きく要件まで確認できる
- ほぼ実際に動く環境に確認できるためブラウザ環境依存などの外部要因も確認できる
- デメリット
- 確認範囲が大きいため、複雑なコードなることが大きくメンテナンスが大変になりがち
- ブラックボックステストで行われるため、バグの発生箇所がわかりづらい
テストコードを導入した方が良いと思うプロジェクト
- 保守まで考慮する息の長いプロジェクト
- 仕様の変更が多い、人の入れ替わりが多いプロジェクト
テストコードを書く理由
- テストのコストを下げるため
- ユニットテストはコードを網羅的に動かすため一度書いてしまうと回帰テストのコストは下げられると思う
- テストをしやすいコードにすることで疎結合なプログラムを意識してかけるようになるため
- テストコード自体がプログラマーに向けての仕様書になるから
- テストコードを考えることによって、要件への理解を深まるから
テスト粒度
- テストコードの重要度は一般的に 単体テスト > 機能テスト > UIテストの順で重要だと言われています。
- 結合テストなどの粒度が大きいテストは仕様の確認もできるため、重要ですが条件が複雑になるため、テストのメンテナンスが煩雑になる、バグが発生した際にどの部分がバグの要因なのかがわかりづらいため、粒度が小さくテストの修正、バグの発見が容易なユニットテストから拡充していくの基本
テストコードがもたらす効果
- プログラマーのストレスを軽減させる
- プログラムはストレスがかかる作業
- 仕様を理解しないといけない
- 正常系だけではなく異常系など色々なこと考慮して書く
- そもそもプログラムでどう表現するかを考えなきゃいけない
- 仕様変わったら、また作りなおさなきゃいけない・・・
- 性能や読み易さも考えなきゃ・・・
- 変更し易さも etc・・・
- いろんな考慮をしながらプログラマーはプログラムを書かなきゃいけない
- 一回でちゃんとしたコードを書くのは非常に難しい。仕様変更がある可能性もある。
- テストコードがないプログラムはどこが壊れるかわからない。
- テストコードを書く
- → リファクタリングした際にどこが壊れたかすぐわかるようにする
- → プログラマーのストレスを下げる
テストコードのコストについて
- テストコードを書くコストは慣れている人で早くて実装の半分、不慣れな人で実装と同じか実装の1.5 ~ 2倍 程度かかると思います。(個人的肌感)
- テストコードがないコードにテストコードを導入する場合はコード本体がテストコードを書かれることが意識されていないため、テストコード導入のコストが多くかかることが多い。
- テストコードは本体のコードが変更された時に合わせて変更するメンテナンスコストも発生する
- テストコードの環境導入、テストコードの書き方自体を学習するコスト
エクストリームプログラミング XP(eXtreme Programming)
- 1990年代に提唱されたウォーターフォール型に変わる開発手法
- 1999年に書籍化されてまとめられた
- 現在のアジャイルと呼ばれる開発手法の大元の一つ
- 4つの価値と5つの基本原則、19のベストプラクティスによって構成されている
- 従来のウォーターフォールは前工程が完全に終わってから次の工程に進んでいく方法と比べて、短いサイクルでの開発を繰り返し、少しずつ改善していく方法に特徴がある
TDD(Test Driven Development)
- テスト駆動開発
- kent beckによってXPのベストプラクティスの中の一つとして提唱された
- XPとテストコード
- XPでは短いサイクルでの開発を前提としていて、常に要件とコードが変化していく、プログラマーはその全てを理解し切ること困難な中でリファクタリングを行っていく。テストコードはその変化していくコードに勇気を持って手を入れるための手助けとなる
- テストファーストと言われる実装する前に先に
失敗するテスト
を書いてから実装を始める手法 - 発展の開発手法にBDDやFDDがある(やったことはないのでよく知らない)
テストファースト
- TDDの基本的な方法論の一つ、実装する前にテストを書き、実装内容のインターフェイスを決定してから実装することで実装の動きを担保し、実装と同時にテストコードもある状態にする手法。
コードに対してRed
→Green
→Refactor
のサイクルを回していき、コードの改善を行っていく - 手順
- メソッド内容IFと出力結果を記載した
失敗するテスト
を書く (Red) -
失敗するテスト
元に実装を進めテストを成功
するようにする (Green) - 完成したテストが失敗しないようにコードの内容を変更、必要によってテストを追加していく(Refactor)
- コードの変更がある場合はまた
失敗するテスト
を書き、テストに合わせてコードを実装しサイクルを回していく
- メソッド内容IFと出力結果を記載した
テストコードフレームワーク
- 基本的にxUnit系が資料を含めて数があるもの多いので特に理由がなければ各言語のxUnit系のものを利用するのがbetter
- 各言語のテストフレームワーク(例)
- PHP
- PHPUnit
- behat
- Java
- jUnit
- JavaScript
- mocha
- ava
- jasmine
- Ruby
- minitest
- RSpec
- Python
- unittest(標準機能に入っている)
- Pytest
CI (継続的インテグレーション)
- テストコードを導入するプロジェクトでは、導入したテストコードを自動的に実行する環境を導入することを提唱されている。jenkinsなどを利用して構築したり、最近ではCircle CIやTravis CIなどのCI機能に特化したサービスなども展開されている。
テストコードを書くために必要なこと
- チームでの協力
- テストコードを書く工数をもらう
- テストコードを書く環境を共通化する
- テストの実行環境を自動化する
- サンプルコードを書いて、他の人でも最初はコピペでも良いので書けるようにする
- テストコードを早めに書くために要件を早めに詳細化する
余談
最近ちょっと意識してやっていること、考えていること
- 実装のMR・PRを作成する際にその実装のテストコード(少なくとも正常系)を一緒につける
- レビュー側もテストコードの内容で実装内容を確認できるし、自分もそのコードの確認のテストを出来て良い。
- テストコードの優先度を考える
- テストをちゃんと拡充することは重要だけど、プロジェクトの進捗度合いや要件の決まり度合いによって、その時点で書くことに利点があるテストを判断する
- 例
- 要件がふわっとしている場合
- まずは要件担保するファンクショナルテストをゆるい検証内容で書いて、要件のfix後に細かい粒度のテストを書くようにする
- 要件がカチッとしている場合
- ユニットテストから書く(書きたい)
- 要件がふわっとしている場合
- テストの範囲と名前、意味について
- 単体テストと機能テストを指す定義の範囲ちゃんと区切りたい。
- 純粋な意味の単体テストならば、他のmethodに依存する部分は全部mockやstubにしてテストするべきなのか。とも思うのですが、確認したい範囲によって、ちょっと結合してやりたいって場合があったりする場合は結合テストという名前になるのか?そうだとしたら結合テストって言葉の指す範囲はあまりに広いなぁと思ったり、もうちょいテストの範囲や意味を整理できる言葉が欲しい。。。
最後に
テストコードは銀の弾丸ではない。
- テストコードには色々なメリットはありますが、決してどんな場面にも有効な銀の弾丸ではありません。
- しかし、
テストコードを書く
と言う勇気ある選択肢を持つことは良い結果をもたらすことがあると思います。 - まだまだ勉強不足で勘違いや不足があると思うので色々編集リクエストをいただけると嬉しいです。