Liebe Leserinnen, liebe Leser,
heute teile ich eine kleine Entdeckung, die ich diese Woche, als ich eine Klasse meines Projektes getestet habe, über Google Test gemacht habe. Wie es die Leute, die GTest benutzt haben, schon wissen, ist es möglich, Tests mit vorgegebenen Daten zu erstellen, um etliche Funktionalität zu überprüfen. Diese Tests heißen parametizierte Testen. Genauso wie der TEST_F Makro, diese Tests verwenden Testvorrichtungen auch. Um es zu schaffen, muss man zuerst ein Testfall mit entsprechenden Instanzvariablen und Konstruktor definieren. Dieser Testfall kann dann für die Erzeugung einer Unterklasse, die von GTest bereitgestellte Template-Klasse erbt, verwendet werden, um die Testvorrichtung zu erstellen. Endlich sollte der Test mithilfe des TEST_P Makros und der Testvorrichtung erstellt werden, und dann das INSTANTIATE_TEST_SUITE_P Makro aufgerufen werden, um die Tests mit den vorgegebenen Daten zu starten. Der unten zu sehende Code stellt das dar:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct ColorCase {
uint32_t test, expected;
ColorCase(uint32_t argTest, uint32_t argExpected)
: test(argTest), expected(argExpected) {}
};
ColorCase rgbToBgrCases[] = {
ColorCase(0xAABBCC, 0xCCBBAA),
ColorCase(0x126754, 0x546712),
ColorCase(0xA6B2C5, 0xC5B2A6)
};
struct ImageTest : public TestWithParam<ColorCase> {
void SetUp() override {
img = ImageClass(30, 30, COLOR_RGB);
// ...
}
ImageClass img;
};
TEST_P(ImageTest, RrbToBgrConversion)
{
// ...
}
INSTANTIATE_TEST_SUITE_P(BulkTest, ImageTest, ValuesIn(rgbToBgrCases));
Das sieht einfach aus, aber was würde passieren, wenn man die gleiche Klasse für die Überprüfung von anderen Funktionalitäten benutzen will? Unter Nutzung der Erfahrungen aus dem TEST_F Makro würde man einen neuen Test mit einem neuen Namen und gleichen Testvorrichtung schreiben, dann würde dieses Test (genauso wie früher) mithilfe des INSTANTIATE… Makro ausgeführt werden:
C++
1
2
3
4
5
6
7
8
9
// ...
ColorCase rgbToYuvCases[] = { ... };
TEST_P(ImageTest, RrbToYuvConversion)
{
// ...
}
INSTANTIATE_TEST_SUITE_P(BulkTest, ImageTest, ValuesIn(rgbToYuvCases));
Das Problem ist, dass es entgegen dem TEST_F Makro nicht funktionieren wird. Der Compiler meldet etwas Ähnliches:
Make
1
2
3
4
5
error: redefinition of 'gtest_ImageTest_EvalGenerator_'
...
error: redefinition of 'gtest_ImageTest_EvalGenerateName_'
...
error: redefinition of 'gtest_ImageTest_dummy_'
Ehrlich gesagt, das war für mich ein bisschen überrachend. Ich habe wirklich gedacht, dass es funktionieren mag. Man sollte nichtsdestoweniger nicht verzweifeln, weil Vererbung kann uns zu Hilfe eilen. Wenn ImageTest als eine Basisklasse, die man direkt nie verwendet, behandelt wird, und dann davon leere Subklassen erzeugt werden, ist es möglich, das obengenannte Problem einfach zu bewältigen:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct ImageTest : public TestWithParam<ColorCase> {
void SetUp() override {
img = ImageClass(30, 30, COLOR_RGB);
// ...
}
ImageClass img;
};
struct ImageTest1 : public ImageTest {};
TEST_P(ImageTest1, RrbToBgrConversion)
{
// ...
}
INSTANTIATE_TEST_SUITE_P(BulkTest, ImageTest1, ValuesIn(rgbToBgrCases));
ColorCase rgbToYuvCases[] = { ... };
struct ImageTest2 : public ImageTest {};
TEST_P(ImageTest2, RrbToYuvConversion)
{
// ...
}
INSTANTIATE_TEST_SUITE_P(BulkTest, ImageTest2, ValuesIn(rgbToYuvCases));
Dieses Verfahren ermöglicht nicht nur die erneute Verwendung der ursprünglichen Klasse, sondern auch die Änderung des Konstruktors von ImageClass durch die Überschreibung von SetUp(). Weitere Änderungen/Ergänzungen der Klasse sind auch möglich. Die Vorteile dieses Verfahrens ist natürlich sichtbarer, wenn die Testvorrichtung mehr Daten oder eine kompliziertere SetUp()-Funktion enthält. Die Lehre des Tags ist:
Die Beschränkungen der parametizierten Tests in Google Test können mit Vererbung überwunden werden.
Wie immer, vielen Dank fürs Lesen.