より踏み込んだ使い方
この章では以下の典型的なテストケースを紹介します。
The following typical test cases are introduced in this chapter.
これらの基本を知ることにより、
組み合わせによる応用や、
さらに高度なテストを行えるようになると思います。
If these are used, a still more advanced test can be performed.
この章ではC言語におけるテストの説明を行っています。
C++言語におけるテストの場合は、オージス総研さんのページが参考になります。
CppUnit 入門
を参照してください。
順番
Sequence
それでは一番単純なテストについて説明していきましょう。
以下のコードに対してテストを行います。
これは足し算を実施する関数で、結果はansに返します。
ただし、この関数は頭が悪いので100を超える数値は
戻り値にエラーを示す値(0以外)を書きます。
This is the easiest test case.
It tests in the following codes.
This is the function which does sums.
A result is returned to ans.
However, this function is foolish.
If 100 is exceeded,An error will be returned.
#include "plus.h"
/** Sums are done. A result is returned to ans.
* However, he is stupid.
* The numerical value to which he exceeds 100
* writes except zero to a return value.
* @param data1 imput paramater 1
* @param data2 imput paramater 2
* @param ans return paramater
* @return 0:normal, Other 0:abnormal
*/
unsigned int plus(
unsigned int data1,
unsigned int data2,
unsigned int *ans) {
unsigned int max = 100;
*ans = data1 + data2;
if ((max <= data1) || (max <= data2) || (max <= *ans)) {
return 1;
}
return 0;
}
このコードに対してヘッダファイルをまず作ります。
良くヘッダファイルを書かない人がいますが、
ソフトウェアテストに限らず、絶対に書いてください。
他の関数からの呼び出し時にヘッダ情報がないと、
コンパイル時に静的なパラメタチェックができません。
A header file is made in this code.
A fool does not write a header file.
A sage writes a header file.
If you do not write a header file,
it will have very bad influence on quality.
#ifndef _PLUS_H_
#define _PLUS_H_
unsigned int plus(
unsigned int data1,
unsigned int data2,
unsigned int *ans);
#endif
ヘッダファイルには、まず以下の記述を入れてください。
これは2重include防止のために使われるもので、
C言語においては常識です。
Please put the following description into a header file.
This is used for double include prevention.
This is common sense.
#ifndef _PLUS_H_
#define _PLUS_H_
/* text */
#endif
C++を使う場合は以下を記述してください。
こうしないと、C++言語でC言語の関数が認識されず、
undefined symbol系のエラーが多発します。
Please describe the following, when you use C++.
#ifdef __cplusplus
extern "C" {
#endif
/* text */
#ifdef __cplusplus
}
#endif
以下はテストコードです。
テストする関数名は、test + 関数名が良いでしょう。
テストするファイル名は、ファイル名+Testが良いでしょう。
これでテストが足りない場合は適宜追加します。
The following is a test code.
As for the function name to test,
"test + function name" is recommended.
As for the file name to test,
"file name + Test" is recommended.
It is recommended that
the file to test and a test file separate a folder.
Supposing you think that a test is inadequate,
a test will be written more.
#include <stdio.h>
#include <testRunner.h>
#include "plus.h"
static unsigned int testPlus(void);
/** Main function. */
int main(void) {
return (int) testRunner(testPlus);
}
static unsigned int testPlus(void) {
unsigned int err;
unsigned int ans;
unsigned int data1;
unsigned int data2;
data1 = 1;
data2 = 1;
err = plus(data1,data2,&ans); // do Test
TEST_ASSERT_EQUALS((data1 + data2),(int)ans);
TEST_ASSERT_EQUALS(err,0);
data1 = 100;
data2 = 1;
err = plus(data1,data2,&ans); // do Test
TEST_ASSERT(err != 0);
data1 = 0;
data2 = 0xffffffffu; // -1
err = plus(data1,data2,&ans); // do Test
TEST_ASSERT(err != 0);
data1 = 1;
data2 = 99;
err = plus(data1,data2,&ans); // do Test
TEST_ASSERT(err != 0);
return 0;
}
上記を見れば分かるとおりCUnit for Mr.Andoではmain()がテストできません。
main()が複数ある場合、コンパイル時に2重登録エラーが発生するためです。
main()の中身をテストした場合は、
一旦中身を外部関数に外だしするといった手法を用いてください。
If the above is seen,
"The test of main() is not made."
will understand in CUnit for Mr.Ando.
また、実際にコーディングするにあたっては、
分かりやすくするため、テスト対象のコードと非テスト対象の
コードは分離した方が良いでしょう(分離したからといって、
テスト環境をCVS環境に登録しないで担当者が変わると
テストコードは闇の中みたいなヤクザなことはさせてはいけませんよ。
そんなこと言い出すプログラマやプロマネは絶対
信用しちゃいけません。作業さぼるに決まってますからッ)。
If possible,separation of the test enviroment.
Mixture is not cool!
図 : テストファイルの分離
Figure : Separation of test folder.
CUnit for Mr.Ando.では、以下の名前空間を使用します。
The following name space is used in CUnit for Mr.Ando.
TEST_ASSERT
Name | Description |
TEST_ASSERT(_a) |
_a が 0 のとき、テストは失敗です。 |
TEST_ASSERT_EQUALS(_a,_b) |
_a == _b のとき、テストは成功です。 |
TEST_ASSERT_NOT_EQUALS(_a,_b) |
_a != _b のとき、テストは成功です。 |
テストが実施されると、
以下のようなメッセージが表示され、
テストが完了したことが確認できます。
The test result is as follows.
$ make run
gcc -ggdb -Wall -I../../CUnitForAndo/include -I../real/include -I./include -c ../real/src/plus.c
gcc -ggdb -Wall -I../../CUnitForAndo/include -I../real/include -I./include -c ./src/plusTest.c
gcc -ggdb -Wall -I../../CUnitForAndo/include -I../real/include -I./include -c ../../CUnitForAndo/src/testRunner.c
gcc -ggdb -Wall -ggdb -Wall -I../../CUnitForAndo/include -I../real/include -I./include -o ./runExe.exe plus.o plusTest.o testRunner.o
./runExe.exe
OK (5 tests)
さて、次のケースでは出力関数を擬似ることにより、
目的の関数のみをテストするものを紹介します。
これは関数callした先の関数の中身が複雑で
容易にテストできなかったり、
まだコーディングできていなかったり、
ハードにアクセスに行くのにそのハードができていなかったりした場合に
有効な手法です。
以下に紹介するファイルでは、sequence()関数内で
先程紹介した plus()を関数callしています。
Next, in the following case, it tests using a Mock function.
Only caller function can be tested by using Mock function.
#include "plus.h"
#include "sequence.h"
unsigned int sequence(
unsigned int data1,
unsigned int data2,
unsigned int *ans) {
unsigned int err;
err = plus(data1,data2,ans);
return err;
}
ここで、関数sequence()が正常に動作することを
確かめるために、出力側の関数である plus()を擬似ります。
ここでは入力値を確かめるために、
inPlus_data1 と inPlus_data2 という変数を用意し、
出力側に outPlus_ans と returnPlus を用意しています。
plus() is set to Mock function in sequence().
Variable inPlus_data1 and inPlus_data2 are prepared for input data.
Variable outPlus_ans and returnPlus are prepared for output data.
#include
#include
#include "plus.h"
#include "sequence.h"
static unsigned int testSequence(void);
/* test data */
static unsigned int inPlus_data1;
static unsigned int inPlus_data2;
static unsigned int outPlus_ans;
static unsigned int returnPlus;
/** Main function. */
int main(void) {
return (int) testRunner(testSequence);
}
static unsigned int testSequence(void) {
unsigned int err;
unsigned int ans;
unsigned int data1;
unsigned int data2;
/////////////////////////////////////////////////
// Normal test
data1 = 0; // 0-0xffffffffu
data2 = 0xffffffffu; // 0-0xffffffffu
inPlus_data1 = 0xffffffffu; // pollute
inPlus_data2 = 0xffffffffu; // pollute
outPlus_ans = 99; // 0-99
returnPlus = 0; // 0(Normal),1-0xffffffffu(Abnormal)
err = sequence(data1,data2,&ans); // run!
TEST_ASSERT_EQUALS(err,(int)returnPlus);
TEST_ASSERT_EQUALS(data1,(int)inPlus_data1);
TEST_ASSERT_EQUALS(data2,(int)inPlus_data2);
TEST_ASSERT_EQUALS(ans,(int)outPlus_ans);
/////////////////////////////////////////////////
// Abnormal test
data1 = 0; // 0-0xffffffffu
data2 = 0xffffffffu; // 0-0xffffffffu
inPlus_data1 = 0xffffffffu; // pollute
inPlus_data2 = 0xffffffffu; // pollute
outPlus_ans = 1; // 0-99
returnPlus = 1; // 0(Normal),1-0xffffffffu(Abnormal)
err = sequence(data1,data2,&ans); // run!
TEST_ASSERT_EQUALS(err,(int)returnPlus);
TEST_ASSERT_EQUALS(data1,(int)inPlus_data1);
TEST_ASSERT_EQUALS(data2,(int)inPlus_data2);
return 0;
}
/** driver */
unsigned int plus(
unsigned int data1,
unsigned int data2,
unsigned int *ans) {
inPlus_data1 = data1;
inPlus_data2 = data2;
*ans = outPlus_ans;
return returnPlus;
}
先ほどの plus.c は一緒にコンパイルすることはできません。
別の環境を用意してください。
これはC言語ではJavaのようなoverwriteができないための制約です。
plus.c cannot be compiled together.
Please prepare another environment.
This is restrictions not to make overwrite like Java in the C language.
図 : テスト環境の分離
Figure : Separation of test environment.
選択
selection
次に選択のケースについて紹介します。
これはぶっちゃけた話 if()文や switch()文に対応するものです。
テストでは入力値を変化させて、
if()文のケースに投入させることで、
その条件のコードが実行されたか、またはされないかを確認します。
下記の被テスト対象コードは、装置種別を受けて、
2つのハードに対し2種類のハードアクセスを実施しています。
It is the test of if() or switch().
This is a file for a test.
#include "select.h"
/** @see select.h */
unsigned int hardAccsess(
unsigned int address,
unsigned int data,
HARD_TYPE_t select) {
unsigned int err = 0;
switch (select) {
case HARD_TYPE_A:
err = setDataA(address,data);
break;
case HARD_TYPE_B:
default:
if ((data & 0xffff0000u) != 0) {
err = 1;
} else {
writeAccessB(address,(unsigned short)data);
}
break;
}
return err;
}
このとき、2つのハードがそれぞれできていないようなとき、
この hardAccsess() をテストしたい場合には以下のような
テストコードを実施しています。ハード装置の A および B
へのアクセス性およびエラー時対応の確認の計4種類です。
This is test file.
#include <stdio.h>
#include <testRunner.h>
#include "select.h"
/* A test is divided. */
static unsigned int testAll(void);
static unsigned int testHardA(void);
static unsigned int testHardB(void);
/* A domain is secured. */
static unsigned int inSetDataA_address;
static unsigned int inSetDataA_data;
static unsigned int returnSetDataA;
static unsigned int inWriteAccessB_address;
static unsigned short inWriteAccessB_data;
int main(int argc,char **argv) {
return (int) testRunner(testAll);
}
static unsigned int testAll(void) {
TEST_ASSERT(! testHardA());
TEST_ASSERT(! testHardB());
return 0;
}
static unsigned int testHardA(void) {
unsigned int err;
unsigned int address;
unsigned int data;
HARD_TYPE_t select;
/////////////////////////////////////////////////
// setDataA normal
address = 0x85000038u; // 0x0-ffffffffu
data = 456; // 0x0-ffffffffu
select = HARD_TYPE_A; // HARD_TYPE_A,HARD_TYPE_B
inSetDataA_address = 0xffffffffu;// imput(dirty)
inSetDataA_data = 0xffffffffu;// imput(dirty)
returnSetDataA = 0; // 0:normal,1-0xffffffffu:abnormal
inWriteAccessB_address = 0xffffffffu; // imput(dirty)
inWriteAccessB_data = 0xffffu; // imput(dirty)
err = hardAccsess(address,data,select); // do test
TEST_ASSERT_EQUALS(err,0); // normal
TEST_ASSERT_EQUALS(inSetDataA_address,(int)address); // Check!
TEST_ASSERT_EQUALS(inSetDataA_data,(int)data); // Check!
TEST_ASSERT_EQUALS(inWriteAccessB_address,(int)0xffffffffu); // no access
TEST_ASSERT_EQUALS(inWriteAccessB_data,0xffffu); // no access
/////////////////////////////////////////////////
// setDataA abnormal
address = 0x86540004u; // 0x0-ffffffffu
data = 22; // 0x0-ffffffffu
select = HARD_TYPE_A; // HARD_TYPE_A,HARD_TYPE_B
inSetDataA_address = 0xffffffffu;// imput(dirty)
inSetDataA_data = 0xffffffffu;// imput(dirty)
returnSetDataA = 0x31u; // 0:normal,1-0xffffffffu:abnormal
inWriteAccessB_address = 0xffffffffu; // imput(dirty)
inWriteAccessB_data = 0xffffu; // imput(dirty)
err = hardAccsess(address,data,select); // do test
TEST_ASSERT(err != 0); // abnormal
TEST_ASSERT_EQUALS(inSetDataA_address,(int)address); // Check!
TEST_ASSERT_EQUALS(inSetDataA_data,(int)data); // Check!
TEST_ASSERT_EQUALS(inWriteAccessB_address,(int)0xffffffffu); // no access
TEST_ASSERT_EQUALS(inWriteAccessB_data,0xffffu); // no access
return 0;
}
static unsigned int testHardB(void) {
unsigned int err;
unsigned int address;
unsigned int data;
HARD_TYPE_t select;
/////////////////////////////////////////////////
// writeAccessB normal
address = 0x36540004u; // 0x0-ffffffffu
data = 22; // 0x0-0000ffffu:normal,0x00010000-0xffff0000:abnormal
select = HARD_TYPE_B; // HARD_TYPE_A,HARD_TYPE_B
inSetDataA_address = 0xffffffffu;// imput(dirty)
inSetDataA_data = 0xffffffffu;// imput(dirty)
returnSetDataA = 0; // 0:normal,1-0xffffffffu:abnormal
inWriteAccessB_address = 0xffffffffu; // imput(dirty)
inWriteAccessB_data = 0xffffu; // imput(dirty)
err = hardAccsess(address,data,select); // do test
TEST_ASSERT_EQUALS(err , 0); // normal
TEST_ASSERT_EQUALS(inSetDataA_address,(int)0xffffffffu); // no access
TEST_ASSERT_EQUALS(inSetDataA_data,(int)0xffffffffu); // no access
TEST_ASSERT_EQUALS(inWriteAccessB_address,(int)address); // Check!
TEST_ASSERT_EQUALS(inWriteAccessB_data,data); // Check!
/////////////////////////////////////////////////
// writeAccessB abnormal
address = 0x36500027u; // 0x0-ffffffffu
data = 0x04000003u; // 0x0-0000ffffu:normal,0x00010000-0xffff0000:abnormal
select = HARD_TYPE_B; // HARD_TYPE_A,HARD_TYPE_B
inSetDataA_address = 0xffffffffu;// imput(dirty)
inSetDataA_data = 0xffffffffu;// imput(dirty)
returnSetDataA = 0; // 0:normal,1-0xffffffffu:abnormal
inWriteAccessB_address = 0xffffffffu; // imput(dirty)
inWriteAccessB_data = 0xffffu; // imput(dirty)
err = hardAccsess(address,data,select);
TEST_ASSERT(err != 0); // abnormal
TEST_ASSERT_EQUALS(inSetDataA_address,(int)0xffffffffu); // no access
TEST_ASSERT_EQUALS(inSetDataA_data,(int)0xffffffffu); // no access
TEST_ASSERT_EQUALS(inWriteAccessB_address,(int)0xffffffffu); // no access
TEST_ASSERT_EQUALS(inWriteAccessB_data,0xffffu); // no access
return 0;
}
/** Unit A Access driver. */
unsigned int setDataA(
unsigned int address,unsigned int data) {
inSetDataA_address = address;
inSetDataA_data = data;
return returnSetDataA;
}
/** Unit B Access driver. */
void writeAccessB(
unsigned int address,unsigned short data) {
inWriteAccessB_address = address;
inWriteAccessB_data = data;
}
これをみて分かる通り、
シーケンスが複雑になればなるほどテストは難しくなります。
ソフトウェアテストを行うにあたっては、
被テスト対象のソースコードはなるべく行数を少なくするということが必要です。
長いソースコードというのは、
可読性を悪くし、良いコーディングスタイルであるとはとても言えませんので、
今一度ファイルを分割する等、工夫を行ってください。
なお、Javaでは一般に1ファイル内のソースコード長は
2,000 line 以内、1メソッドはエディタの1画面に納まる程度のライン行に
収めるべきと言われています。
繰り返し
Foreach
順番、選択の次は繰り返しです。
これでソフトウェア3大制御が一通り説明できたことになります。
以下のコードは、ループによりハードの初期化を行う簡単なプログラムです。
This is a file for a test.
#include "select.h"
#include "loop.h"
unsigned int loop(void) {
unsigned int err;
unsigned int loopErr = 0;
unsigned int i = 0;
for (i = 0 ; i < 10 ; i++) {
unsigned int address = 0x85000000u + ( i + 8);
err = setDataA(address,i);
if (err && (! loopErr)) {
loopErr = err;
}
}
return loopErr;
}
繰り返しテストの場合は、
改ざんする出力側のコードの処理がより複雑になります。
今回のケースは単純1重ループですが、
ループのやりかたが複雑化した場合は、
テストコードはより複雑になっていくことになります。
This is test file.
#include <stdio.h>
#include <string.h>
#include <testRunner.h>
#include "select.h"
#include "loop.h"
/* A domain is secured. */
#define DATA_ARRAY 100
static unsigned int inSetDataA_address[DATA_ARRAY];
static unsigned int inSetDataA_data[DATA_ARRAY];
static unsigned int returnSetDataA[DATA_ARRAY];
static unsigned int countSetDataA = 0;
static unsigned int testLoop(void);
/** Main function. */
int main() {
return (int) testRunner(testLoop);
}
static unsigned int testLoop(void) {
unsigned int err;
unsigned int i;
/////////////////////////////////////////////////
// normal
memset(inSetDataA_address,0xffffffffu,sizeof(unsigned int)*DATA_ARRAY);
memset(inSetDataA_data,0xffffffffu,sizeof(unsigned int)*DATA_ARRAY);
memset(returnSetDataA,0,sizeof(unsigned int)*DATA_ARRAY);
countSetDataA = 0;
err = loop(); // do test
TEST_ASSERT_EQUALS(err,0); // normal
TEST_ASSERT_EQUALS(countSetDataA,10); //
for (i = 0 ; i < 10 ; i++) {
TEST_ASSERT_EQUALS(inSetDataA_address[i] ,(int) (0x85000000u + (i + 8)));
TEST_ASSERT_EQUALS(inSetDataA_data[i] ,(int) i);
}
/////////////////////////////////////////////////
// abnormal
memset(inSetDataA_address,0xffffffffu,sizeof(unsigned int)*DATA_ARRAY);
memset(inSetDataA_data,0xffffffffu,sizeof(unsigned int)*DATA_ARRAY);
memset(returnSetDataA,0,sizeof(unsigned int)*DATA_ARRAY);
returnSetDataA[3] = 5; // A mistake is mixed.
countSetDataA = 0;
err = loop(); // do test
TEST_ASSERT(err != 0); // abnormal
// Even if there is an error, a hard setup has run
TEST_ASSERT_EQUALS(countSetDataA,10);
for (i = 0 ; i < 10 ; i++) {
TEST_ASSERT_EQUALS(inSetDataA_address[i] ,(int) (0x85000000u + (i + 8)));
TEST_ASSERT_EQUALS(inSetDataA_data[i] ,(int) i);
}
return 0;
}
/** Unit A Access driver. */
unsigned int setDataA(
unsigned int address,unsigned int data) {
inSetDataA_address[countSetDataA] = address;
inSetDataA_data[countSetDataA] = data;
countSetDataA++;
return returnSetDataA[countSetDataA];
}
ando@park.ruru.ne.jp