Apr 22, 2009

HeteroVector for software testing.

I spend almost 3 days making this simple package, HeteroVector.

HeteroVector is a kind of std::vector. A difference is that HeteroVector can contain different types as 2 dimensional array. Let me give you an simple test case here:
int arg1[ ] = { 1, 2 };
char arg2[ ] = "amz";
float arg3[ ] = { 1.1f, 2.2f };

typedef LOKI_TYPELIST_3(int, char, float) MyInputTypes; // from Loki library

HeteroVector< MyInputTypes > values;
values.set< 0 >( arg1 );
values.set< 1 >( arg2 );
values.set< 2 >( arg3 );

ASSERT_EQ(1, values.get< 0 >( 0 ));
ASSERT_EQ(2, values.get< 0 >( 1 ));
ASSERT_EQ('a', values.get< 1 >( 0 ));
ASSERT_EQ('m', values.get< 1 >( 1 ));
ASSERT_EQ('z', values.get< 1 >( 2 ));
ASSERT_EQ(1.1f, values.get< 2 >( 0 ));
ASSERT_EQ(2.2f, values.get< 2 >( 1 ));
Here the object "values" represent this 2 dimensional array.
Note that it contains three different types. Each row can contain different type but each columns in a same row must be the same type. The type is forced by compiler so that nobody can make mistake; it was possible because I was using template meta programming.

You may noticed that the way to access each value is not usual. For example, the code accessing the value 1 is "values.set < 0 > ( 0 );" The first value, which is for "row", is not method argument, but template argument so that it is "<" and ">". The second value is the method argument. The value for the template argument is technically type indicator, which is "int, char, float" in this case.

Let's go one more step. A testing code for "Factorial" is below:
int factorial(int nThStep) {
if(nThStep == 0) return 1;
return factorial(nThStep -1) * nThStep;
}

TEST(HeteroVector, Factorial) {
// This example shows how input and output values can be paired.
typedef LOKI_TYPELIST_2(int, int) FactorialTypes;
const unsigned int input = 0;
const unsigned int output = 1;

HeteroVector< FactorialTypes > inputs_outputs;
int inputs[] = { 0, 1, 2, 3, 4 };
int outputs[]= { 1, 1, 2, 6, 23 };
inputs_outputs.set< input >( inputs );
inputs_outputs.set< output >( outputs );
inputs_outputs.replace< output >( 4, 24 );

for(unsigned int nTh = 0; nTh < inputs_outputs.size< input >>(); ++nTh)
ASSERT_EQ(inputs_outputs.get< output >(nTh),
factorial( inputs_outputs.get< input >(nTh) ) );
}
The data for testing is all managed by the HeteroVector. The variable "inputs_outputs" represent this 2 dimensional array:
Note that the values for the template arguments can be replaced by meaningful name. In next example, I will show you that we can also use "enum".

Here you may think we don't need to use HeteroVector for this factorial function testing. We can also replace it with two vectors, which will be "vector inputs", and "vector outputs". But it is just an example that I want to show how to use.

Let me give you an example for testing Switch function.
bool switchOnOff( bool switch1, bool switch2, bool switch3 ) {
return switch1 && (switch2 || switch3);
}
The function, switchOnOff, requires three boolean arguments. Therefore we need to test 8 cases ( = 2 * 2 * 2 ). I drawn a table here:My additional implementation for HeteroVector generate those Combination of three "true/false" values. The testing code is here:
TEST(CoverageTest, AllCombinationsCoverage) {
typedef LOKI_TYPELIST_3(bool, bool, bool) Booleans;
enum { switch1, switch2, switch3 };
HeteroVector< Booleans > inputs;

bool true_false[] = { true, false };
bool true_only[] = { true };

inputs.set< switch1 >( true_false );
inputs.set< switch2 >( true_only );
inputs.set< switch3 >( true_false );
const bool expected[] = {
/*TTT*/ true,
/*FTT*/ false,
/*TTF*/ true,
/*FTF*/ false };

HeteroVector< Booleans > ACoC = allCombinationsCoverage(inputs);
ASSERT_EQ(sizeof(expected)/sizeof(bool), ACoC.size< switch1 >());
ASSERT_TRUE( ACoC.size< switch1 >() == ACoC.size< switch2 >() );
ASSERT_TRUE( ACoC.size< switch2 >() == ACoC.size< switch3 >() );

for(unsigned int idx = 0; idx < ACoC.size< switch1 >(); ++idx)
ASSERT_EQ( expected[idx],
switchOnOff(
ACoC.get< switch1 >(idx),
ACoC.get< switch2 >(idx),
ACoC.get< switch3 >(idx)
) ) < < idx; }
This code is a little bit different from the table, because I fixed the second argument in a value, "true," in order to show that we don't always need to generate for every possible cases but we can select some input values for our interest. By the constrain the code generate only 4 cases ( 2 * 1 * 2), but we can, of course, generate 8 cases easily.

Note that the template argument is replaced by ENUM values, which makes easier to read.

I have also implemented Base Choice Coverage, which is introduced in my professors book, "Introduction to Software Testing" (written by Paul Ammann and Jeff Offutt)

The basic idea of Base Choice Coverage is that we choose a default set of input values and change only one value from it rather than using all combination. Therefore the input set of BCC will be like this table:
The testing case will give you how to use my implementation for BCC:
TEST(CoverageTest, BaseChoicesCoverage) {
typedef LOKI_TYPELIST_3(bool, bool, bool) Booleans;
enum { switch1, switch2, switch3 };
HeteroVector< Booleans > inputs;

bool true_false[] = { true, false };
bool false_only[] = { false };

inputs.set< switch1 >( true_false );
inputs.set< switch2 >( true_false );
inputs.set< switch3 >( false_only );
const bool expected[] = {
/*TTF*/ true,
/*FTF*/ false,
/*TFF*/ false };

HeteroVector< Booleans > BCC = baseChoiceCoverage(inputs);
ASSERT_EQ(sizeof(expected)/sizeof(bool), BCC.size< switch1 >());
ASSERT_TRUE( BCC.size< switch1 >() == BCC.size< switch2 >() );
ASSERT_TRUE( BCC.size< switch2 >() == BCC.size< switch3 >() );

for(unsigned int idx = 0; idx < BCC.size< switch1 >(); ++idx)
ASSERT_EQ( expected[idx],
switchOnOff(
BCC.get< switch1 >(idx),
BCC.get< switch2 >(idx),
BCC.get< switch3 >(idx)
) ) < < idx; }
The code is, again, different from the table that I showed above. The testing code fixed 3rd value at "false". Therefore it generates only 3 cases but it is, again, easy to generate 4 cases, which satisfies BCC correctly. I just wanted to show the implementation has flexability for choice.

I spent 3 days implementing this code. It was very hard because I used template meta programming which gives programmers desperate. In other word, my implementation strongly check Type. Compiler will forece users not to make mistake at compile time not at run-time. And it also increases run-time speed, because many parts of calculation will be done at compile time.

I like to share my effort with everybody. Any comments are welcome. You can download the source code here: HeteroVector.

I used Loki library, which is famous meta programming library. It is open source so that anybody can download; in fact my implementation requires Loki library. You can find the most recent version of Loki here.

1 comment:

Anonymous said...

although I cannot understand, but I know how happy you are with this achievements.

Congratulations! Hope you can have more and more joy in the world of programming~

Yours,
Rong Rong