テスト容易性を向上させるための7つの特性について
はじめに
ソフトウェア開発において、テスト容易性を上げることは品質と生産性に繋がります。
テスト容易性を上げることで、システム全体の不具合を早期に発見できたり、品質を効率よく担保できるはずです。また、テストがしやすいコードは、レビューがしやすく、不具合の修正も迅速に行えるため、開発者の負担軽減にも繋がります。
本記事では、テスト容易性を構成する7つの特性について、良い例と悪い例を交えながら解説していきます。
テスト容易性とは
テスト容易性は、ソフトウェアテストがどれだけやりやすいかを表す特性です。
テストを効率的に行うために、必要な条件がどれだけ整っているか。
実践ソフトウェアエンジニアリングでは、以下の7つの特性から構成されるとされています。
- 実行円滑性(Operability)
- 観測容易性(Observability)
- 制御容易性(Controllability)
- 分解容易性(Decomposability)
- 単純性(Simplicity)
- 安定性(Stability)
- 理解容易性(Understandability)
実行円滑性(Operability)
テストをどれだけ簡単に実行できるかどうかを表す特性です。
悪い例
- ドキュメントが不十分で、テストの準備に時間がかかってしまう。
- テスト環境が一貫していない
- 開発者ごとにテスト環境が異なり、実行結果に一貫性がなくなる。
- テストの実行時間が長い
良い例
- ドキュメントが十分にあり、誰でも簡単にテストの準備を完了できる
- テスト環境が一貫している
- Dockerなど仮想マシンを使用して、誰でも同じテスト環境を用意できる
- テスト実行の効率化
観測容易性(Observability)
テスト対象の状態がどれだけ把握しやすいかどうかを表す特性です。
悪い例
- システムの状態やエラーの内容が曖昧
- エラーを握りつぶしている
- 状態を観測する手段がなく、問題発生時に原因特定が困難
- ドキュメントや手順が不足している
良い例
- ドキュメントやアラートに具体的な情報が含まれている
- エラーを握りつぶさずに観測可能な状態になっている
- モニタリングツールがあり、状態を観測する手段がある
制御容易性(Controllability)
テスト対象の操作のしやすさで、ソフトウェアの動作を外部からコントロールしやすいかどうかを表す特性です。
悪い例
- 外部依存を直接参照するコード
- 異常系や特定の条件をテストしづらい
良い例
- 外部依存をモックに変更できるなど、操作可能になっている
外部依存には、本番環境のDBや外部API、ライブラリなどが挙げられます。
例えば、以下のように外部APIを呼び出すコードがあるとき、以下の状態だと外部のAPIサーバーの状態や、ネットワーク状況にテスト結果が左右されます。
const fetchUserData = async (userId) => {
const response = await fetchFunction(`https://api.example.com/users/${userId}`)
if (!response.ok) {
throw new Error('Failed to fetch user data')
}
return response.json()
}
これを以下のように、DI(依存性の注入)を活用して、fetchFunctionを引数で受け取るようにすると、テスト環境ではモック関数を使用できるようになり、テスト環境が安定します。
const fetchUserData = async (userId, fetchFunction = fetch) => {
const response = await fetchFunction(`https://api.example.com/users/${userId}`)
if (!response.ok) {
throw new Error('Failed to fetch user data');
}
return response.json()
}
分解容易性(Decomposability)
テスト対象を分割して、それぞれ独立してテストできるかどうかを表す特性です。
悪い例
- 密結合で凝集度が低い
- 外部依存を直接使用している
- 複数の責務を1つの関数で使用している
良い例
- 疎結合で凝集度が高い
- DIが活用されている
- 単一責任の原則が守られている
単純性(Simplicity)
コードがシンプルで理解しやすく、不要な複雑さがないかどうかを表す特性です。
悪い例
- 密結合で凝集度が低い
- 複雑な条件分岐
- 受け取るパラメータが多い
良い例
- 疎結合で凝集度が高い
- シンプルな条件分岐
- 受け取るパラメータの数が適切
安定性(Stability)
テストに与える影響の少なさや、テスト結果が一貫しているかどうかを表す特性です。
悪い例
- 冪等性がない関数
- 外部依存を直接参照する
- 頻繁に変更が必要なコード
- テストの変更も頻繁に必要になる
良い例
- 冪等性がある関数
- DIが活用されている
- 頻繁に変更が必要ないコード
仕様変更が頻繁に行われるなどで、コードの変更が多く必要な場合は、より安定したインタフェースを通してテストできるようにすると安定性が向上します。
理解容易性(Understandability)
シンプルにコードやテスト対象がどれだけ理解しやすいかを表す特性です。
悪い例
- 無意味な変数名や関数名
- シグネチャが適切でない関数
- コメントがない、コメントに頼りすぎる
良い例
- 意味のある変数名や関数名が使われている
- シグネチャが適切な関数
- 適切なコメントが書かれている
- コメントがなくても理解できる
まとめ
各特性について、それぞれの概要と良い例、悪い例を並べましたが、テスト容易性は設計やコードの一般原則を守ると向上するものが多いことが分かります。
- 疎結合にして、凝集度を高くする
- 単一責任の原則を守る
- DIを活用する
- 分かりやすい変数名、関数名にする
- etc
はじめに書いたように、テスト容易性の向上は単にテスト効率を上げるだけでなく、ソフトウェアの品質やチーム全体の開発体験も向上させる効果があります。
僕自身もまだまだできていない部分があるため、日々の開発において、この考え方をより取り入れて役立てていこうと思います。