1

ターゲット ハードウェアで実行せずに組み込み C コードの単体テストを行っています。コードの一部を次に示します。

uint8 tempReadback = 0;

write_to_i2c( msg->addres, msg->value );


     tempReadback = read_from_i2c( msg->addres);

     if( tempReadback == msg->value )
     {
        somethingA;
     }

     else
     {
        somethingB;
     } 

関数write_to_i2c()は、特定のレジスタに値を書き込みます。関数read_from_i2c()は、レジスタから値を読み取ります。さらに、変数tempReadbackを使用して、読み戻された値が書き込まれた値と同じかどうかを比較しています。これまでのところ問題なく、これはターゲット ハードウェアで動作します。現在、ターゲット ハードウェア (Software in the Loop) でコードを実行せずに Uni テストを行っています。つまり、式tempReadback == msg->valueが真になることはなく (tempReadback は 0)、毎回ステートメントsomethingBで実行されます。レジスタの読み取りを偽造する方法はありますか? フレームワークとして CppUTest を使用しています。

感謝します!

4

2 に答える 2

3

CppUTestは、組み込み C 開発に最適です。これは、無料の関数をモックできる唯一のテスト フレームワークであるためです (この場合はwrite_to_i2c()read_from_i2c())。

ここで、CppUTest のドキュメントまたは優れた本Test Driven Development for Embedded Cを実際に読んでいる必要があります。

とにかく、次のコードはそれを行う方法を示しています。

コンパイル可能な方法で書かれたテスト対象ユニット (UUT) (次に SO で質問するときは、努力してください):

#include "temperature.h"
#include "i2c.h"

void somethingA(void) { }
void somethingB(void) { }

void temperature_do(uint8_t address, uint8_t value) {
    write_to_i2c(address, value);
    const uint8_t tempReadback = read_from_i2c(address);
    if (tempReadback == value) {
        somethingA();
    } else {
        somethingB();
    }
}

あなたが書いたように、「偽造」する必要があります。より正確には「モック」する必要がwrite_to_i2c()ありread_from_i2c()ます。モックを別のファイル (i2c_mock.cpp など) に入れて、単体テストをビルドするときに、実際の実装ではなくモックに対してリンクするようにします。

extern "C" {
#include "i2c.h"
};

#include "CppUTestExt/MockSupport.h"

void write_to_i2c(uint8_t address, uint8_t value) {
    mock().actualCall(__FUNCTION__)
        .withParameter("address", address)
        .withParameter("value", value);
}

uint8_t read_from_i2c(uint8_t address) {
    mock().actualCall(__FUNCTION__)
        .withParameter("address", address);
    uint8_t ret = mock().returnIntValueOrDefault(0);
    return ret;
}

詳細については、CppUMock のドキュメントを参照してください。これは単なる古典的な CppUMock ボイラープレートです。

最後の部分は単体テストです。

extern "C" {
#include "temperature.h" // UUT
};

#include "CppUTest/TestHarness.h"
#include "CppUTest/CommandLineTestRunner.h"
#include "CppUTestExt/MockSupport.h"

TEST_GROUP(Temperature)
{
    void setup() {}
    void teardown() {
        mock().checkExpectations();
        mock().clear();
    }
};

TEST(Temperature, somethingA)
{
    const uint8_t value = 10;
    mock().ignoreOtherCalls();
    mock().expectOneCall("read_from_i2c").ignoreOtherParameters()
        .andReturnValue(value);
    temperature_do(10, value);
}

TEST(Temperature, somethingB)
{
    const uint8_t value = 10;
    mock().ignoreOtherCalls();
    mock().expectOneCall("read_from_i2c").ignoreOtherParameters()
        .andReturnValue(value+1);
    temperature_do(10, value);
}

int main(int argc, char** argv) {
    return CommandLineTestRunner::RunAllTests(argc, argv);
}

この UT は、実際には 100% のブランチ カバレッジを提供します。繰り返しますが、すべての詳細を説明することはできません。somethingAテスト ケースとを観察して比較するとsomethingB、UUT が を呼び出すパスに 1 回入り、 を呼び出すパスに 1 回入るには何が必要かがわかりsomethingA()ますsomethingB()

例を挙げましょう

mock().expectOneCall("read_from_i2c")
    .ignoreOtherParameters()
    .andReturnValue(value+1);

ここで、 CppUmock に function の呼び出しを期待しread_from_i2c()、パラメータが何であるかを無視し、これは基本的に重要であり、返すvalue + 1(または とは異なるその他の何か) を返すように言っていますvalue。これにより、UUT は を呼び出すパスに入りますsomethingB()

組み込み C 開発と単体テストをお楽しみください。

于 2016-04-21T20:54:34.350 に答える
0

TDD とモック オブジェクトに関する提案された本を調べます。OK、私が理解しているように、たとえばこの行で(モックはすでに作成されています):

mock().expectOneCall("read_from_i2c").ignoreOtherParameters()
    .andReturnValue(value);
temperature_do(10, value);

プログラムは、テスト対象のユニットからの実際のパラメーターではなく、自分で定義したパラメーターを使用して、モック化された「read_from_i2c」関数 (i2c_mock.cpp から) に「ジャンプ」しますか? それとも、Unit Under Test から実際に関数を呼び出しますが、モックで定義されたパラメーターを使用してこの関数を操作しますか?

于 2016-04-29T09:28:11.843 に答える