ソフトウェアテストの運用

2025/03/01
Contents

はじめに

この記事では、ソフトウェアテストの運用時の注意点について解説します。

ここで扱うテスト

単体テスト、結合テスト等開発者側で実施するテストを対象とします。そのため、受け入れテストなど顧客側で実施するテストはここでは扱いません。

対象読者

  • テストコードの運用方法を学びたい人
  • テストの結果の見方を理解したい人

本記事で扱う用語の定義

用語意味
プロダクションコード実際のアプリケーションやシステムで使用されるコード。
テストコードプロダクションコードをテストするためのコード。
リファクタリング外部から見た振る舞いを変えずに実装を変更すること。
モック外部依存部分をシミュレートし、テストのために差し替えたオブジェクト。外部APIやデータベースの動作を模擬するために使用されます。

テスト実施の目的

ソフトウェアの寿命を延ばすことが目的です。 近年、開発したソフトウェアはリリース後も継続的にアップデートすることが一般的です。 特にインターネットが普及してからは更新プログラムを配布したり、WEBアプリケーションであればクラウドサービスにデプロイしたりするなど、ソフトウェアのアップデートは当たり前になりました。 しかし、アップデート作業のコストはソフトウェアが成長するほど徐々に大きくなっていきます。

適切なテストをしないとどうなるのか

ここでは適切なテストをしないことによってどのようにソフトウェアの寿命が短くなるかを説明します。

機能追加が難しくなる

機能の追加に伴い新規機能周辺のプロダクションコードの変更を行うことがあります。周辺プロダクションコードのテストを実施していなかった場合、新規のバグが発生しても気づくことが難しくなります。

リファクタリングが難しくなる

リファクタリングの定義からわかるように、外部から見た振る舞いを変えないことが必要です。テストを実施しなければ、振る舞いが変化しなかったことを保証することが難しいでしょう。 さらに、リファクタリングが困難になることで、プロダクションコードの複雑さを解消できなくなり、新規機能追加も困難になります。

テストは銀の弾丸ではなく、コストがかかる

テストを実施することはソフトウェアの寿命を伸ばすために不可欠であることを説明しましたが、テストするにもコストがかかります。 特に開発初期は機能追加やリファクタリングも容易であり、テストを実施するコストがとても大きく感じます。 さらに、テストは「一度作って放置」という運用ができません。そのため、テストを実施する際はプロダクションコードだけでなくテストをどのように維持するかも考慮する必要があります。

テスト結果の見方

テスト結果は単純に成功したか失敗したかを見るだけでは不十分です。そこで、次の4パターンを考えます。テストコードが正しいとは限らないことに注意してください。

  1. プロダクションコードが正しく、テストも成功した
  2. プロダクションコードに誤りがあり、テストも失敗した
  3. プロダクションコードが正しくないのに、テストが成功した
  4. プロダクションコードが正しいのに、テストが成功した

1、2のパターンではテストの結果とプロダクションコードの正しさは整合性が取れているため、現時点では問題になりません。しかし3、4のパターンは問題が発生します。 このケースについて詳しく見ていきましょう。

プロダクションコードが「正しくないのにテストが成功」した

この結果はプロダクションコードに潜在的なバグが混入していることを示しています。 この場合、バグが顕在化するまで気づかないことが多いため、防ぐためにはテストの実装時に注意する必要があります。

テストケースが不十分な場合

この場合はテストケースを改善することでテストの強度を高めることができます。 改善策の1つに境界値分析というものがあります。境界値分析は、「入力データの境界値付近でバグが発生することが多いため、境界値を重点的にテストするべき」という考えに則るものです。

年齢をもとに成年かを判定するプログラムを実装した場合を考えます。このプログラムは、「年齢を表すint型の引数ageを受けとり、成年の場合はtrueを返し、未成年の場合はfalseを返す。負数など、不正な引数の場合は例外をスローする」という仕様です。 境界値分析では、実行結果が変化する入力データを考えるので、ageが17または18、0、-1である時である時を考えれば良いです。このことから、次のようなケースを考えます。

  • if age = -1 => throw exception
  • if age = 0 => return false
  • if age = 17 => return false
  • if age = 18 => return true

今回扱っていませんが、null値が与えられたり、int型以外の値が与えられたりする場合も考慮した方が良いです。 この考慮の必要性は使用言語の型付け(動的型付け、弱い静的型付け等)によって異なるため、注意してください。

外部依存を置き換えたテストの場合

バグが混入する原因は自分(自身が所属するチーム)が書いたコードとは限りません。 外部ライブラリや、DBアクセス部分など、外部依存を置き換えてテストすることは多くありますが、これは置き換えられたコードが正しく動作する前提であることを忘れてはいけません。 「置き換えることをやめる」という対策方法はありますが、その分テストコードの保守にかかるコストは増加します。

プロダクションコードは「正しいのにテストが失敗」した

この結果はテストコード側に問題がある可能性があることを示しています。テストコードが論理的に間違っている場合は修正すればいいですが、 特に、次の特徴をもつテストコードはプロダクションコードの変更時に失敗しやすいです。

  • プロダクションコードの内部構造に基づいたテスト(ホワイトボックステスト)
  • モックを利用しており、モックが持つ処理を「呼び出しているか」をテストしている

決してこれらが完全に悪いものではないですが、テストが壊れやすくなることを理解した上で利用する必要があります。 テストが壊れやすくなると、リファクタリングも困難になることに注意してください。

テストの優先順位

テストを実装する際は優先順位を考える必要があります。

  1. 仕様通りに振る舞うか
  2. 異常な値を与えた際に安全か
  3. 必要な処理を正しく呼び出しているか等、内部実装に関するテスト

大まかな順位ですが、大体は1、2だけ検証すれば事足ります。もしモックを利用したテストやホワイトボックステストをしている際は、 3の「必要な処理を正しく呼び出しているか等、内部実装に関するテスト」をする必要があるのかをよく検討しましょう。 テストでは厳密さも大事ですが、柔軟さも大事です。これらはトレードオフの関係なので何を優先してテストしたいかを考えることは非常に重要です。

自動テストはテストを維持する観点でも重要

自動テストとは、何らかのイベントをトリガーにして自動的にテストが実行される仕組みです。何らかのイベントの例として、GitHubにプッシュしたり、プルリクエストを作成したりすることが挙げられます。自動テストはプロダクションコードの品質を維持するための仕組みとして紹介されることが多いですが、テストコードの品質を維持する観点でも非常に重要な仕組みです。

自動テストのメリット

一番のメリットは、プロダクションコードの変更時に必ずテスト結果を確認できるようにできることです。 テストが失敗した場合、プロダクションコードかテストコードのどちらかが誤っていることに気づくことができるため、修正することができます ただし、「プロダクションコードが正しくないのにテストが成功した」場合を検知することはできないため、注意が必要です。

参考文献

  • Vladimir Khorikov著、須田智之訳、「単体テストの考え方/使い方」(2023)