Parametrierbare Tests mit CppUnit

System Engineering 15. Juni 2018

Es gibt einige C++ Unit Testing Frameworks. Das wohl bekannteste und meistverwendete ist CppUnit. Leider bietet es bei weitem nicht den Funktionsumfang wie sein Vorbild JUnit, was zum Teil auch mangelnder Eigenschaften der Programmiersprache C++, wie z.B. Reflection, geschuldet ist. Ein solches Feature ist z.B. die Erstellung parametrierbarer Unit Tests. Mit JUnit lässt sich solch ein Test relativ einfach implementieren. Es genügt die Unit Test Klasse mit @RunWith(Parameterized.class) zu annotieren, sowie eine Methode, annotiert mit @Parameters, welche die Testdaten als Collection<Object[]> bereitstellt, zu implementieren.

Wie eingangs erwähnt unterstützt C++ einige nützliche Eigenschaften moderner Programmiersprachen nicht, darunter auch Annotations. Dies ist zwar kein Grund parametrierbare Tests nicht zu unterstützen, dennoch hat man es bislang versäumt CppUnit mit der Unterstützung für parametrierbare Tests zu versehen. Dieser Artikel stellt eine Lösung vor, wie parametrierbare Tests für das CppUnit Framework erstellt werden können, die annähernd den selben Komfort wie jene mit JUnit bieten.

Parametrieren bedeutet in diesem Kontext, dass ein und dieselbe Test­-Methode mit den einzelnen Elementen eines Parametersatzes aufgerufen wird. Dies bietet sich dann an, wenn die Implementierung einzelner Tests, bis auf bestimmte Parameter, gleich ausfällt.

Für diesen Zweck haben wir CppUnit um diverse Makros, die zur Definition von Test-Suiten verwendet werden, erweitert. Die Makros setzen voraus, dass zwei Methoden, samt Implementierung, bereitgestellt werden

[c]
static std::vector parameters();
void testWithParameter(ParameterType& parameter);
[/c]

CPPUNIT_PARAMETERIZED_TEST_SUITE

[c]
CPPUNIT_PARAMETERIZED_TEST_SUITE ( TestFixtureType, ParameterType )
[/c]

Das Makro erweitert CPPUNIT_TEST_SUITE um den Parameter ParameterType. Dieser legt den Typ der Parameter fest.

Beispiel:

[c]
CPPUNIT_PARAMETERIZED_TEST_SUITE ( MyTest, std::string )
[/c]

CPPUNIT_PARAMETERIZED_TEST_SUITE_END

[c]
CPPUNIT_PARAMETERIZED_TEST_SUITE_END
[/c]

Dieses Makro wurde lediglich der Konsistenz halber eingeführt. Es entspricht dem Makro CPPUNIT_TEST_SUITE_END.

CPPUNIT_PARAMETERIZED_TEST_SUITE_REGSITRATION

[c]
CPPUNIT_PARAMETERIZED_TEST_SUITE_REGISTRATION ( TestFixtureType, ParameterType )
[/c]

Dieses Makro erweitert CPPUNIT_TEST_SUITE_REGISTRATION um den Parameter ParameterType. Es stellt die Implementierung der Testablauflogik zur Verfügung.

Beispiel:

[c]
CPPUNIT_PARAMETERIZED_TEST_SUITE_REGISTRATION ( MyTest, std::string )
[/c]

Beispiel

ParameterizedTest.hpp

[c]
class ParameterizedTest: public CPPUNIT_NS::TestFixture {
public:
ParameterizedTest();
~ParameterizedTest();
void setUp();
void tearDown();
/**
* Retrieves the test parameters. Each entry is passed as a single parameter * to a test.
*
* @return the parameters list.
*/
static std::vector parameters();
/**
* This method is called with a single test parameter.
*
* @param parameter The parameter to be used for the test.
*/
void testWithParameter(std::string parameter);
void testOrdinaryTest();
private:
CPPUNIT_PARAMETERIZED_TEST_SUITE(ParameterizedTest, std::string);
CPPUNIT_TEST(testOrdinaryTest);
CPPUNIT_PARAMETERIZED_TEST_SUITE_END();
};
[/c]

ParameterizedTest.cpp

[c]
CPPUNIT_PARAMETERIZED_TEST_SUITE_REGISTRATION(ParameterizedTest, std::string);
ParameterizedTest::ParameterizedTest() {
// empty
}
ParameterizedTest::~ParameterizedTest() {
// empty
}
void ParameterizedTest::setUp(){
// empty
}
void ParameterizedTest::tearDown(){
// empty
}
static std::string buildParameter(size_t testNumber){ std::ostringstream result;
result &lt;&lt; “Parameter ” &lt;&lt; testNumber;
return result.str();
}
std::vector ParameterizedTest::parameters(){
std::vector result; for(size_t i = 0; i &lt; 3; i++){
result.push_back(buildParameter(i)); return result;
}
}
void ParameterizedTest::testWithParameter(std::string param){
CPPUNIT_ASSERT_EQUAL(buildParameter(m_currentTestNumber), param);
}
/**
* This test should be called after all parameterized tests.
*/
void ParameterizedTest::testOrdinaryTest(){
CPPUNIT_ASSERT_EQUAL(m_parameters.size(), m_currentTestNumber);
}
[/c]

Diese Unit Test Klasse definiert zwei Test-­Methoden: testWithParameter und testOrdinaryTest. testWithParameter wird mit einem Parameter vom Typ std::string aufgerufen. Diese werden über die Methoden parameters bereitgestellt. testOrdinaryTest soll verdeutlichen, dass neben parametrierten Tests auch “herkömmliche” Tests definiert und ausgeführt werden können. Dieser Test prüft ob der aktuelle Test­zähler der Anzahl der Parameter entspricht.

Hinweis:

Die o.g. Makros definieren interne Variablen und Methoden, u.A. auch m_currentTestNumber. Diese Variable beginnt bei 1 an zu zählen und wird nach jedem parametrierten Test inkrementiert. Sie gibt also die Nummer des aktuellen Tests an.

Es folgt der gesamte Inhalt der Datei Parameterized.hpp

[c]
#ifndef PARAMETERIZED_HPP_
#define PARAMETERIZED_HPP_
&nbsp;
#include &lt;cppunit/extensions/HelperMacros.h&gt;
&nbsp;
/**
* Extends the macro CPPUNIT_TEST_SUITE in order to easily specify a parameterized
* unit test.It expects the following methods to be specified by the implementing
* unit test:&lt;br&gt;
* &lt;ul&gt;
* &lt;li&gt;&lt;code&gt;static std::vector&lt;ParameterType&gt; parameters();&lt;/code&gt;&lt;/li&gt;
* &lt;li&gt;&lt;code&gt;void testWithParameter(ParameterType&lt;/code&gt;&lt;/li&gt;
* &lt;/ul&gt;
*
* @param ATestFixtureType Type of the test case class.
* @param ParameterType the type of a single parameter.
*
* see CPPUNIT_TEST_SUITE
*/
#define CPPUNIT_PARAMETERIZED_TEST_SUITE( TestFixtureType, ParameterType )
void __test();

static std::vector&lt;ParameterType&gt; m_parameters;
static size_t m_currentTestNumber;

CPPUNIT_TEST_SUITE(TestFixtureType);

m_parameters = parameters();
m_currentTestNumber = 0;

for (size_t i = 0; i &lt; m_parameters.size(); i++) {
std::ostringstream testName;
testName &lt;&lt; “testWithParameter: ” &lt;&lt; i;

CPPUNIT_TEST_SUITE_ADD_TEST(
( new CPPUNIT_NS::TestCaller&lt;TestFixtureType&gt;(
context.getTestNameFor( testName.str() ),
&amp;TestFixtureType::__test,
context.makeFixture() ) ));
}
&nbsp;
/**
* Used to declare the end of a parameterized test suite.
*/
#define CPPUNIT_PARAMETERIZED_TEST_SUITE_END CPPUNIT_TEST_SUITE_END
&nbsp;
/**
* Extends the macro CPPUNIT_TEST_SUITE_REGISTRATION in order to provide
* necessary framework code in implementation file. This code actually implements
* the parameterization behavior.
*/
#define CPPUNIT_PARAMETERIZED_TEST_SUITE_REGISTRATION( TestFixtureType, ParameterType )
CPPUNIT_TEST_SUITE_REGISTRATION(TestFixtureType);

std::vector&lt;ParameterType&gt; TestFixtureType::m_parameters;
size_t TestFixtureType::m_currentTestNumber;

void TestFixtureType::__test(){

testWithParameter(m_parameters.at(m_currentTestNumber++));
}
#endif /* PARAMETERIZED_HPP_ */
[/c]