概要
既存のプロジェクトでソフトウェアテスト(ユニットテストや統合テスト)をテストサイズごとに分別するための手法を紹介します。
テストサイズによるテストの分別自体は新しい手法ではありませんが既存のプロジェクトに適用するのは多くの困難を伴います。
本記事ではテストケースを分類する意義や自動分類の方法、直面した課題と解決策についてまとめました。
なぜテストを分類する必要があるのか?
テストの実行時間が長かったり確率的に失敗したりすると、つい面倒になってテストの実行を省略したりすることはありませんか?
アプリケーションが拡張されていくのに合わせてテストコードも増えていくとテストの量が増えて実行時間も長くなっていきます。
アプリケーションのパフォーマンスに比べてテストのパフォーマンスの問題は解決が後回しにされがちです。
なぜならテストのパフォーマンスはユーザに直接的な影響がないからです。
しかし、テストのパフォーマンスが悪化することでテストが頻繁に実行されなくなると、長期的にはアプリケーションの品質も悪化することにつながります。
テストサイズによってテストを分類することで、分類したテストの実行時間を短くしテストを安定化させることができます。
テストサイズによる分類のメリット
例えば、以下のような5つのテストケースがあります。ここでは簡単のためにテストは直列に実行されるものとします。
4つのテストはそれぞれ5秒で実行できますが、1つの行儀の悪いテストは300秒ほどかかります。
合計すると320秒かかってしまいます。
これを2つの集合に分類します。5秒のテストをSmall、300秒のテストをLargeとします。
20秒で実行できるSmallを頻繁に実行して、300秒かかるLargeの実行頻度を下げます。
分類された一部のテスト実行時間が短く保たれることで、テストが全く実行されない状況を減らせるはずです。
具体的なテストの分類
Google Testing Blog ではテストをSmall、Medium、Largeの3つに分類しています。
たとえばSmallテストはネットワークやデータベース、ファイルシステムへのアクセスがなく、テスト実行時間の上限が60秒と決められています。
つまり外的要因による不安定さがなく短時間で実行できるテストとそうでないテストを分類しています。
一般的に「ユニットテスト」や「統合テスト」には厳密な定義がなく曖昧に分類されて運用されやすいです。
テストの曖昧な呼称をやめて機能の有無によって厳密に分類するとテストのパフォーマンスを維持しやすくなります。
既存のテストの分類
これから新しくテストを書いていく場合にはテストの分類の条件を決めて書くべきです。
それでは既に書かれた未分類のテストはどう分類すればいいでしょうか?
分類の方針
おおまかには以下のような方針で分類していきます。
- 分類の条件を決める
- 分類数
- 分類ごとの条件(ネットワークへのアクセスの有無など)
- コストをかけずに分類する
- 既存のテストケースを機械的に分類する
- できれば自動化したい
- タグやアノテーションで分類する
- テストは複数の属性を持つのでカテゴリで一意に分類するのは現実的ではない
- 速度とのトレードオフなのでテストが多すぎる場合には必ずしもこの限りではない
- 自動分類できなかったものは大きい分類に入れておく
- あとから個別に分類すればよい
- 小さい分類(Small)のテストケースを増やしていく
具体的なテスト分類手法
テストの数が少なければ手動で分類できるかもしれませんが、量が多ければ分類にコストがかかります。
自動で分類できれば分類のコストを減らすことができます。
ここでは機能ごとに自動でテストを分類する手法を紹介します。
ネットワーク
- tcpdumpを使う
https://www.tcpdump.org/manpages/tcpdump.1.html- tcpdump コマンドでネットワークへのアクセスを監視できます。
- たとえば8080番ポート以外の外部へのネットワークアクセスを見るには以下のようなコマンドを使います。
tcpdump -Q out src port !8080
データベース
- データベースの認証/接続を落とす
- データベースに接続できなくしてエラーが出ないことを確認しましょう。
- (リモートデータベースの場合) tcpdumpを使う
- リモートデータベースの場合にはネットワークへのアクセスを監視する方法もあります。
ファイルシステム
- 読み出し/書き込み権限を外してテストする
http://man7.org/linux/man-pages/man2/chmod.2.html- アクセスするディレクトリへの読み出し/書き込み権限を外してエラーが出ないことを確認しましょう。
実行時間
- テストフレームワークの時間計測オプションを有効にする
- 現代的なテストフレームワークの多くには標準で実装されています。parseしやすい出力フォーマットやレポートオプションを選択しましょう。
- timeコマンドを使う
http://man7.org/linux/man-pages/man2/time.2.html
time -p /path/to/run/test
テストの実行
テストサイズごとにコマンド(またはサブコマンド)を変えて実行できるようにします。
たとえば node.js のテストフレームワーク mocha の場合には以下のようにします。
まずテストの分類にはテストケースの名前に特定のタグを入れます (@small
, #small
など)
テストサイズごとに実行するには以下のコマンドは名前に特定のタグ名が含まれるテストケースだけを実行します。
mocha --grep @small
ローカルのテスト実行環境だけではなく、CIのワークフローを変えたほうが良いでしょう。
たとえば以下のようにします。
- small はローカルでもCIでもできる限り毎回実行する
- medium はCIでfeatureブランチに対して実行する
- large はCIでメインブランチへのマージまたはデプロイ前に実行する
テスト分類時に遭遇した課題
実際にテストケースを分類してみて遭遇した課題と解決策を紹介します。
- タグをつけるのが大変
テストケースにタグをつけるのはあえて自動化しなかったので、手動でタグをつけるのにはそれなりの時間がかかりました。自動化しようと思えばできたかもしれません。テストフレームワークによっては簡単な場合もありそうです。 - アプリケーションの不具合が大量に見つかった
これは課題というより良かったことですが、露見していなかったアプリケーションの不具合やパフォーマンスの問題がいくつか見つかりました。やはりテストは正しく実行すべきだと改めて思いました。 - テストの起動が遅い
理想的にはタグだけで分別したいけど、全部のテストファイルをロードするだけで時間がかかっていました。行儀の悪いテストファイルをロードするだけでテストを実行しなくても時間を浪費していました。smallテストを含むファイルだけを指定することで高速化しました。たとえば test ディレクトリ以下にある@small
タグをテストケースに含むファイルだけを抽出するには以下のコマンドを使います。
grep @small -rl test/