web-dev-qa-db-ja.com

C ++で記述された組み込みソフトウェアのテスト可能なクラス/インターフェース

テスト可能なクラス/インターフェースを作成するための可能な解決策に関するフィードバックを収集したいと思います。

通常のC++開発では、参照/ポインタと前方宣言を使用して、動的なポリモーフィズムを実現できます(以下の「参照によるソリューション」を参照)。

ただし、特定の種類の組み込みソフトウェアには、動的メモリ、仮想呼び出し、delete/freeなどの使用を禁止する(または推奨しない)制約があるため、通常の動的ソフトウェアを使用することはできません。ポリモーフィズム。この場合、ポリモーフィズムを実現するための唯一のソリューションはテンプレートを使用することのようです(以下の「テンプレートを使用したソリューション」を参照)。このソリューションは良いもののようですが、コードが常にヘッダーファイルにある必要があり、後でコンパイル時間が長くなる可能性があります。

私はテスト可能なクラス/インターフェースを持つことを可能にする他の解決策が存在するのか疑問に思っています:

  • 実際のハードウェアを使用している場合は、実際のドライバー実装を使用します
  • テストするときは、モックドライバークラスを使用します。
  • 実際のインターフェースと模擬インターフェースを簡単に切り替えることができます(「依存性注入」アプローチ)。

私の質問の焦点は、組込みソフトウェアが常に上記のすべての制約を持つ必要があるかどうかを議論することではなく、多態性をアーカイブするための可能な解決策にあります。 virtualの使用を避けたい場合、どのようにしてテスト可能なクラス/インターフェイスをまだ持つことができますか?


参照によるソリューション

// Driver.h
class Driver {
public:
  virtual void doHardwareCall() = 0;
};

// DriverUser.h
class Driver;
class DriverUser {
  Driver &driver;

public:
  DriverUser(Driver &driver);

  void foo();
};

// DriverUser.cpp
DriverUser::DriverUser(Driver &driver) : driver(driver) {}
void DriverUser::foo() {
  driver.doHardwareCall();
}

// Test file
#include "DriverUser.h"

class MockDriver : public Driver {
  void doHardwareCall() override {
    std::cout << "MockDriver::doHardwareCall()" << std::endl;
  }
};

TEST(DriverUser, usingMockDriver) {
  MockDriver mockDriver;
  DriverUser driverUser(mockDriver);

  driverUser.foo(); // => outputs "MockDriver::doHardwareCall()"
}

テンプレートを使用したソリューション

// Driver.h
class Driver {
public:
  void doHardwareCall();
};

// DriverUser.h
class Driver;

template <typename T>
class DriverUser {
  T &driver;
public:
  DriverUser(T &driver) : driver(driver) {}
  void foo() {
    driver.doHardwareCall();
  }
};

// Test file
#include "DriverUser.h"

class MockDriver : public Driver {
public:
  void doHardwareCall() {
    std::cout << "MockDriver::doHardwareCall()" << std::endl;
  }
};

TEST(DriverUser, usingMockDriver) {
  MockDriver mockDriver;
  DriverUser<MockDriver> driverUser(mockDriver);

  driverUser.foo();
};
2

ただし、動的メモリ、仮想呼び出し、削除、空き容量などの制約が埋め込まれているため、ポリモーフィズムを実現するための唯一の解決策はテンプレートを使用することのように見えます

参照のあるソリューションでは、動的割り当て(new)を使用する必要はありませんが、仮想関数を使用する必要があります。コーディング標準によって仮想関数も禁止されている場合(組み込み開発の場合でも常にそうであるとは限りません)、テンプレートは実際にコードベースで何らかの形のポリモーフィズムを取得する唯一の方法です。

私の経験では、埋め込まれたC++コードのテストを作成する2つの主要な方法があります

  1. 「参照付きソリューション」のように、インターフェースと参照を使用します。ターゲットで実行されるコードの場合、関連するオブジェクトが静的に作成され、接続されます

    // in main.cpp
    HardwareDriver driver;
    DriverUser driverUser(driver);
    

    単体テスト自体については、ダイナミックアロケーションの禁止から例外が発生する可能性があります。これにより、同じセットアップコードを何度も繰り返すことなく、異なるテストが互いに干渉しないようにすることが容易になります。

  2. ターゲットまたは単体テスト用にコードが構築されているかどうかに応じて、実装コードには異なる.cppファイルを使用します。この場合、「driver.h」ヘッダーファイルが1つあり、ビルドシステムは、driver_target.cppまたはdriver_mocks.cppを使用して、Driverクラスの適切な実装を取得します。
    これは、一部のコードがユニットテストされないことを意味しますが、そのコードはおそらくターゲットハードウェアに固有であるため、ユニットテストに使用されるシステムでコンパイルできない可能性が高いと考えてください。