7

私が取り組んでいるタイプの組み込みプログラミングでは、実行中のコードの決定論と透過性が高く評価されます。透明性とは、たとえば、メモリの任意のセクションを見て、そこに格納されている変数を知ることができるということです。したがって、組み込みプログラマーが期待していると確信しているように、 new は可能な限り回避する必要があり、回避できない場合は初期化に限定されます。

私はこれが必要であることは理解していますが、同僚がこれを行う方法には同意しませんし、より良い代替案も知りません。

構造体のいくつかのグローバル配列といくつかのグローバル クラスがあります。ミューテックス用に 1 つ、セマフォ用に 1 つ、メッセージ キュー用に 1 つの構造体の配列があります (これらはメインで初期化されます)。実行されるスレッドごとに、それを所有するクラスがグローバル変数になります。

これに関する最大の問題は、単体テストにあります。テストしたいクラスが#includeグローバル変数をテストしていない場合、モックオブジェクトを挿入するにはどうすればよいですか?

擬似コードの状況は次のとおりです。

foo.h

#include "Task.h"
class Foo : Task {
public:
  Foo(int n);
  ~Foo();
  doStuff();
private:
  // copy and assignment operators here
}

bar.h

#include <pthread.h>
#include "Task.h"

enum threadIndex { THREAD1 THREAD2 NUM_THREADS };
struct tThreadConfig {
  char      *name,
  Task      *taskptr,
  pthread_t  threadId,
  ...
};
void startTasks();

bar.cpp

#include "Foo.h"

Foo foo1(42);
Foo foo2(1337);
Task task(7331);

tThreadConfig threadConfig[NUM_THREADS] = {
  { "Foo 1", &foo1, 0, ... },
  { "Foo 2", &foo2, 0, ... },
  { "Task",  &task, 0, ... }
};

void FSW_taskStart() {
    for (int i = 0; i < NUMBER_OF_TASKS; i++) {
        threadConfig[i].taskptr->createThread(  );
    }
}

タスクを増やしたり減らしたりしたい場合はどうすればよいですか? foo1 のコンストラクター内の異なる引数のセットですか? bar.h と bar.cpp を別々に用意する必要があると思いますが、これは必要以上に手間がかかるようです。

4

3 に答える 3

4

このようなコードの単体テストを最初に行いたい場合は、レガシー コードを効果的に使用する方法を読むことをお勧めします。これも参照してください。

基本的にリンカーを使用してモック/フェイク オブジェクトと関数を挿入するのは最後の手段ですが、それでも完全に有効です。

ただし、フレームワークなしで制御の反転を使用することもできます。これにより、クライアント コードに責任が押し付けられる可能性があります。しかし、それは本当にテストに役立ちます。たとえば、テストするFSW_taskStart()

tThreadConfig threadConfig[NUM_THREADS] = {
  { "Foo 1", %foo1, 0, ... },
  { "Foo 2", %foo2, 0, ... },
  { "Task",  %task, 0, ... }
};

void FSW_taskStart(tThreadConfig configs[], size_t len) {
    for (int i = 0; i < len; i++) {
        configs[i].taskptr->createThread(  );
    }
}

void FSW_taskStart() {
    FSW_taskStart(tThreadConfig, NUM_THREADS);
}

void testFSW_taskStart() {
    MockTask foo1, foo2, foo3;
    tThreadConfig mocks[3] = {
          { "Foo 1", &foo1, 0, ... },
          { "Foo 2", &foo2, 0, ... },
          { "Task",  &foo3, 0, ... }
        };
    FSW_taskStart(mocks, 3);
    assert(foo1.started);
    assert(foo2.started);
    assert(foo3.started);
}

これで、スレッドのモック バージョンを 'FSW_taskStart' に渡して、関数が実際に必要に応じてスレッドを開始するようにすることができます。残念ながら、オリジナルが正しい引数を渡すという事実に依存する必要FSW_taskStart がありますが、現在はより多くのコードをテストしています。

于 2009-08-13T21:58:57.663 に答える
3

依存性注入はあなたの状況で役立ちますか? これにより、すべてのグローバル変数が削除され、単体テストで依存関係を簡単に置き換えることができます。

各スレッドのメイン関数には、依存関係 (ドライバー、メールボックスなど) を含むマップが渡され、それらを使用するクラスに格納されます (一部のグローバル変数にアクセスする代わりに)。

各環境 (ターゲット、シミュレーター、単体テストなど) に対して、必要なすべてのオブジェクト、ドライバー、およびすべてのスレッドを作成する 1 つの "構成" 関数を作成し、スレッドに依存関係のリストを提供します。たとえば、ターゲット構成は USB ドライバーを作成し、それを一部の通信スレッドに挿入できますが、通信ユニット テスト構成は、テストが制御するスタブ USB ドライバーを作成できます。

重要な変数にこの「透過性」が絶対に必要な場合は、既知のアドレスにそれらを保持するクラスを作成し、必要な場所にこれらのクラスを挿入します。

これは、オブジェクトの静的リストよりもかなり手間がかかりますが、特に複雑な統合の問題に遭遇し、テストのためにコンポーネントを交換したい場合には、柔軟性は素晴らしいものです。

だいたい:

// Config specific to one target.
void configure_for_target_blah(System_config& cfg)
{   // create drivers
    cfg.drivers.push_back("USB", new USB_driver(...))
    // create threads
    Thread_cfg t;
    t.main = comms_main; // main function for that thread
    t.drivers += "USB"; // List of driver names to pass as dependencies
    cfg.threads += t;
}

// Main function for the comms thread.
void comms_main(Thread_config& cfg)
{
    USB_driver* usb = cfg.get_driver("USB");
    // check for null, then store it and use it...
}

// Same main for all configs.
int main()
{
    System_config& cfg;
    configure_for_target_blah(cfg);
    //for each cfg.drivers
    //    initialise driver
    //for each cfg.threads
    //    create_thread with the given main, and pass a Thread_config with dependencies
}
于 2009-08-14T09:12:35.023 に答える
-1

malloc を使用してメモリを割り当て、 new 演算子を取得してその位置にオブジェクトを作成できます

void* mem = malloc(3*sizeof(SomeClass));
SomeClass *a = new(mem) SomeClass();
mem += sizeof(SomeClass);
SomeClass *b = new(mem) SomeClass();
mem += sizeof(SomeClass);
SomeClass *c = new(mem) SomeClass();

そのため、すべてのメモリを malloc してから、必要に応じて割り当てることができます。注:delete を呼び出したときはそうではないので、手動で分解を呼び出すようにしてください。

于 2009-08-14T08:08:02.640 に答える