バグ取りから発生する「エンバグ」を防ぐ、回帰テスト

組み込みソフトウェア開発の現場では、「バグを直したら、別の機能が正しく動かなくなってしまった」というトラブルがよく発生します。

「タイマーの処理を修正したら通信が止まってしまった」「センサの初期化を変更したらモーター制御がおかしくなった」、そんな事例は珍しくありません。

このように、修正や変更によって別の場所に新しいバグが生まれることを「エンバグ」と呼びます。

本記事では、エンバグの基礎知識と、エンバグを防ぐための「回帰テスト」について解説します。

なぜ組み込みソフトウェアでエンバグが起きやすいのか?

エンバグ(enbug)とは、バグを修正したり新しい機能を追加したりする際に、意図せず別の場所に新しいバグを作り込んでしまうことです。英語の「エン(en)」には「中に入れる」という意味があり、バグを取り除く「デバッグ(debug)」の対義語として使われています。
とくに組み込みソフトウェアは、限られたメモリ、CPUリソースで動作するため、複数の機能が密接に関連し合っています。例えば、タイマー割り込み、通信処理、センサ制御などが同じ変数を参照していたり、共通のバッファを使用していたりすることがよくあります。このような環境では、一つの関数を修正しただけで、思わぬところに影響が及ぶことがあります。

エンバグは、思ってもみないところで不具合が起きるという性質から、「気づきにくい」という特長があります。気づきにくいということは見つけるまでに時間がかかる不具合になります。不具合は埋め込んでから見つかるまでの時間が長ければ長いほど、修正と検証に時間がかかります。そのため、エンバグをなるべく早く見つけるような検証の仕組みが必要になります。

例えば、あるエアコンの制御ソフトウェアで、温度チェックのタイマー処理を10ミリ秒から5ミリ秒に変更したところ、リモコンとの赤外線通信が不安定になるというバグが発生しました。原因を調べたところ、タイマー割り込みの頻度が上がったことで、通信処理の実行タイミングがずれてしまい、正しくデータを受信できなくなっていたのです。

タイマーと通信は一見無関係に見えますが、組み込みシステムでは割り込み処理の優先度やタイミングが密接に関係しているため、このようなエンバグが発生します。過去のバージョンで動いていた機能が、エンバグによって正しく動作しなくなることを、デグレード(デグレ)といいます。

バグ修正から意図しないバグが発生する際のイメージ図

組み込みソフトウェアのエンバグは深刻な問題を起こす

とくに組み込みソフトウェアでは、エンバグが特に深刻な問題となります。パソコンのソフトウェアであれば、インターネット経由でアップデートを配信できますが、家電製品や産業機器には、そのような仕組みを持っていないものも多くあります。一度出荷された製品に、エンバグが原因の不具合が発生した場合、製品のリコールや現地での修理が必要になることもあります。
また組み込みシステムは実機でしか確認できない動作も多く、デバッグに時間がかかることも珍しくありません。そのため出荷後にエンバグが発覚すると、原因の特定と修正に多大な工数を要することになるのです。

出荷後の話を先にさせていただきましたが、開発中でもエンバグの早期発見は重要です。ソフトウェアの品質は、設計、実装、テスト、修正と段階的に作り上げていくものです。修正段階で混入してしまったエンバグをテストで見つけられないと、「テストが正しくできているのか?」という、品質を作り上げていくうえでの根本的な課題が出てきてしまいます。テストの品質に疑いが出ると、開発計画に対しての影響は大きくなります。エンジニアにとってエンバグと、それによるデグレードは非常に怖いものになります。

「変更前と同じように動くか」を検証する「回帰テスト」の有効性

そうしたエンジニア泣かせのエンバグを防ぐのに有効なのが、「回帰テスト(リグレッションテスト)」です。
回帰テスト(リグレッションテスト)とは、ソフトウェアの変更や修正をおこなった後に、既存の機能が正しく動作し続けているかを確認するテストのことです。「回帰」とは、元の状態に戻ることを意味し、変更前と同じように動作するかを検証します。

回帰テストの目的は、「変更したところ」だけでなく、「その周りの機能」や「関連する可能性がある機能」も含めて、もう一度確認することです。変更した機能そのものが正しく動作していても、その影響で別の機能に不具合を起こしていないかをチェックします。

例えば、温度センサの値を読み取る関数を修正した場合、その関数を直接呼び出している部分だけでなく、その温度データを使って制御している部分、温度データを表示している部分なども、正しく動作するかを確認します。

回帰テストと確認テストの違い

回帰テストと混同されやすいのが「確認テスト」です。確認テストは、修正したバグが本当に直っているかを確認するテストです。一方、回帰テストは、その修正によって他の機能に影響が出ていないかを確認するテストです。
つまり、バグ修正後には、「確認テスト(修正した箇所が直っているか)」と「回帰テスト(他の機能に影響を及ぼしていないか)」の両方が必要になります。

回帰テストを実施するときの3つのポイント

組み込みソフトウェアで回帰テストを効果的におこなうには、以下の3つのステップを踏むことが重要です。

1. 変更影響範囲をメモする

まず最初にやるべきことは、変更した箇所が影響を与える可能性がある範囲を洗い出すことです。これを「影響範囲分析」と呼びます。
変更した関数を呼び出している場所、変更した変数を参照している場所、変更したモジュールと連携している機能など、影響が及ぶ可能性がある箇所をリストアップします。このとき、ソースコードの検索機能や、関数の呼び出し関係を可視化するツールを活用すると便利です。
例えば、センサの初期化関数を修正した場合、次のような項目をメモします。

  • センサ初期化関数を直接呼び出している箇所
  • センサの値を読み取る関数
  • センサデータを使用して制御をおこなう機能
  • センサの状態を画面に表示する機能

このメモが、回帰テストの範囲を決める基礎資料となります。

2. 自動化しやすい単体テストから始める

回帰テストを全部手動で実施するのは、時間も工数もかかりすぎて現実的ではありません。そこで、自動化しやすい部分から少しずつ自動化していくことが重要です。
最も自動化しやすいのは、単体テスト(ユニットテスト)です。個々の関数単位でのテストであれば、ハードウェアに依存しない部分も多く、比較的簡単に自動化できます。まずは、変更した関数とその周辺の関数について、単体テストを作成し、自動実行できるようにします。

3. 実機で全体動作確認をおこなう

単体テストだけでは、ハードウェアとの連携や、複数の機能が組み合わさった動作を確認できません。そのため、最終的には実機でのテストが必要になります。ただし、すべてのテストケースを毎回実施するのは非効率です。影響範囲分析で洗い出した重要な機能や、過去にバグが多発した機能を優先的にテストします。実機での回帰テストも、手作業で繰り返しおこなうと工数がかかり、非効率になります。なるべく自動化をできるように検討するとともに、自動化をできない手動テストも効率的におこなえるように、テスト環境を整えることも重要です。

また、テストの結果は必ず記録に残すようにします。「どのテストケースを実施したか」「結果はどうだったか」を記録しておくことで、後で問題が発生したときに、テスト漏れがなかったかを確認できます。

まとめ

回帰テストを正しく理解し実践することで、「変更前はきちんと動いていたのに…」というトラブルを防ぎ、高品質な製品を安定してリリースできるようになります。ぜひ回帰テストの重要性を理解し、テスト計画の段階で回帰テストを実施できるような計画にして、日々の開発に活かしていただきたいと思います。

組み込みソフトの世界 トップへ戻る