組み込みC開発者向けの優れた単体テストの例[終了]


20

来週、ユニットテストとテスト駆動開発について私の部署に講演する予定です。その一環として、最近書いたコードから実際の例をいくつか紹介しますが、講演で書く非常に簡単な例をいくつか紹介したいと思います。

私はウェブ上で良い例を探してきましたが、開発分野に特に当てはまるものを見つけるのに苦労していました。私たちが作成するソフトウェアのほとんどは、小型のマイクロコントローラーで実行される埋め込み型の制御システムです。ユニットテストに簡単に適用できる多くのCコードがあります(ターゲット自体ではなくPCでユニットテストについて説明します)。「下」の層から直接離れている限り:直接話すものマイクロコントローラー周辺機器に。しかし、私が見つけたほとんどの例は文字列処理に基づいている傾向があります(たとえば、優れたDive Into Python Romanの数字の例)。文字列を使用することはほとんどないため、これは実際には適切ではありません(コードが通常使用する唯一のライブラリ関数について)ありmemcpymemcmpそしてmemsetstrcat または正規表現は正しくありません)。

では、質問に答えてください。ライブセッションで単体テストのデモンストレーションに使用できる機能の良い例を提供してくれる人はいますか?私の(変更される可能性がある)意見での適切な答えは、おそらく次のとおりです。

  • 誰でも(たまにコードを書くだけでも)理解できるほど単純な関数。
  • 無意味に見えない関数(つまり、パリティまたはCRCを計算することは、おそらく2つの数値を乗算してランダムな定数を追加する関数よりも優れています)。
  • 人の部屋の前に書くのに十分短い関数(エラーを減らすためにVimの多くのクリップボードを利用するかもしれません...);
  • 文字列を処理するのではなく、数値、配列、ポインター、または構造体をパラメーターとして受け取り、類似のものを返す関数。
  • 単純なエラー(例えば)>ではなく、簡単に入力できる関数は、>=ほとんどの場合は動作しますが、特定のエッジケースで破損する可能性があります。ユニットテストで簡単に識別および修正できます。

何かご意見は?

おそらく関連性はありませんが、テスト自体はおそらくGoogle Test Frameworkを使用してC ++で記述されます。すべてのヘッダーには既に#ifdef __cplusplus extern "C" {ラッパーがあります。これは、これまでに行ったテストでうまく機能しました。


ここで「問題」を取り上げて、TDDを経営陣に販売するためのプレゼンテーションを考え出すと、これは望ましい形式に合理的にうまく適合するように思えます。OPは、この問題に対する既存のソリューションを要求しているようです。
テクノフィル

回答:


15

以下は、lenバイトにわたってチェックサムを生成することになっている単純な関数です。

int checksum(void *p, int len)
{
    int accum = 0;
    unsigned char* pp = (unsigned char*)p;
    int i;
    for (i = 0; i <= len; i++)
    {
        accum += *pp++;
    }
    return accum;
}

フェンスポストのバグがあります。forステートメントでは、テストはである必要がありますi < len

面白いのは、このようなテキスト文字列に適用すると...

char *myString = "foo";
int testval = checksum(myString, strlen(myString));

「正解」が得られます!これは、チェックサムされた余分なバイトがゼロ文字列ターミネーターだったためです。したがって、このチェックサム関数をコードに入れて、場合によっては出荷することもできます。問題に気付くことはありません。つまり、テキスト文字列以外に適用するまでです。

これは、このバグにフラグを立てる単純な単体テストです(ほとんどの場合... :-)

void main()
{
    // Seed the random number generator
    srand(time(NULL));

    // Fill an array with junk bytes
    char buf[1024];
    int i;
    for (i = 0; i < 1024; i++)
    {
        buf[i] = (char)rand();
    }

    // Put the numbers 0-9 in the first ten bytes
    for (i = 0; i <= 9; i++)
    {
        buf[i] = i;
    }

    // Now, the unit test. The sum of 0 to 9 should
    // be 45. But if buf[10] isn't 0 - which it won't be,
    // 255/256 of the time - this will fail.
    int testval = checksum(buf, 10);
    if (testval == 45)
    {
        printf("Passed!\n");
    }
    else
    {
        printf("Failed! Expected 45, got %d\n", testval);
    }
}

とても良い!これは私が望んでいた答えの一種です。ありがとう。
DrAl

バッファを作成するとき、そのメモリのチャンクにすでにゴミがあります。実際に乱数で初期化する必要がありますか?
スネークサンダース

@SnakeSandersユニットテストをできるだけ決定的にしたいので、私はイエスと言います。使用するコンパイラが開発者のマシンに0を置き、テストマシンに10を置くと、バグを見つけるのにひどい時間がかかるでしょう。同じ理由で、固定シードではなく時間に依存させるのは悪い考えだと思います。
アンドリューは、Reinstate Monica

単体テストで非決定的な振る舞いに依存するのは悪い考えです。
不安定

2

バブルソートのようなソート関数の実装についてはどうですか?ソート機能が動作したら、単体テストとTDDを導入するのにちょうど良いバイナリ検索を続行できます。

並べ替えと検索は比較に依存するため、簡単に間違えられます。また、注意して実行する必要があるポインターの交換も伴います。どちらもエラーが発生しやすいので、ごちゃごちゃにしてください:)

さらにいくつかのアイデア:

  • ユニットテストは、リファクタリングを行うときに非常に役立ちます。したがって、バブルソートが機能するようになったら、それをのようなより強力なソートに変更することができます。qsortテストは引き続きパスし、新しいソート機能も機能することが証明されます。
  • 並べ替えはテストが簡単で、結果は並べ替えられるか、並べ替えられないため、適切な候補になります。
  • 検索でも同じです。存在するか、または存在しません。
  • ソート用のテストを作成すると、テストに使用する入力の種類(ゼロ要素、ランダム入力、重複エントリ、巨大な配列など)などの議論が始まります。

テストがどのように人生を楽にするかを示す簡単なエラーについて、具体的な提案はありますか?
DrAl

@DrAl:それで私の答えを更新しました。
マーティンウィックマン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.