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 };Here the object "values" represent this 2 dimensional array.
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 ));
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) {The data for testing is all managed by the HeteroVector. The variable "inputs_outputs" represent this 2 dimensional array:
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) ) );
}
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
Let me give you an example for testing Switch function.
bool switchOnOff( bool switch1, bool switch2, bool 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:
return switch1 && (switch2 || switch3);
}
TEST(CoverageTest, AllCombinationsCoverage) {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.
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; }
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) {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.
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; }
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.