The Skeleton of a C++ Testing Framework

XGG Blog

https://github.com/CyberZHG/CppTesting

Introduction

It took me one day to write [this]((https://github.com/CyberZHG/CppTesting) personal C++ testing framework. What I want is a JUnit-like unit test, in which one can inherit from a base unit test case, and write test cases in the inherited class. Due to the limitation of the C++ grammar, template and macro have to be used. I will introduce the solution I used to implement the testing framework with assertions.

Pattern

What I desire looks like below:

ztest;class ExampleUnitTest : public UnitTest {public:virtual void setUp() override final {}virtual void setUpEach() override final {}virtual void tearDownEach() override final {}virtual void tearDown() override final {}protected:int inClassValue;};__TEST_U(ExampleUnitTest, test1) {__ASSERT_EQ(this->inClassValue, 1);}__TEST_U(ExampleUnitTest, test2) {__EXPECT_TRUE(true);}int main(){Framework* framework = Framework::getInstance();framework->runTests();return 0;}

Actually, it is a compromise due to the C++ grammar. By using the macro __TEST_U, the test case is added to the testing framework automatically. setUp() and tearDown() will be invoked before and after running tests in the test suite respectively, while setUpEach() and tearDownEach() will be called each time a single test case being tested. The test cases in the same test suite will not influence each other, in the view point of each test case, only setUp() is invoked before setUpEach().

The code above has the following result:

Adding Tests Automatically

To automatically add test cases, dynamic initialization of global static variables could be used.

For example:

;int func() {cout << “Invoked in runtime.” << endl;return 1;}static int v = func();int main() {return 0;}

v will be evaluated at runtime (v will not be optimized to 1 even in -O3), thus we can use func() to add test cases because func() is invoked before the execution of main().

The macro __TEST_U looks like a definition of function, actually, the code __TEST_U(ExampleUnitTest, test1) looks like below after expanded:

class __TEST_CLASS_ExampleUnitTest_test1_ : public ExampleUnitTest {public:virtual void test() override final;static int addToTestCases() {// Construct testSuite and testCase…Framework::getInstance()->addTestSuite(“ExampleUnitTest”, testSuite);Framework::getInstance()->addTestCase(“ExampleUnitTest”, “test1”, testCase);return 0;}};static int __TEST_CONST_ExampleUnitTest_test1_ = __TEST_CLASS_ExampleUnitTest_test1_::addToTestCases();void __TEST_CLASS_ExampleUnitTest_test1_::test()

Just ignore testSuite and testCase, by using __TEST_U macro, we create a child class inheriting ExampleUnitTest, and create a static function to add the current test case to the testing framework (Framework is a singleton class). After the class definition is a static variable, which will be dynamic initialized and the static function in the child class will be invoked. The last line declared the function name in the child class, so when writing a test case, one actually created a child class and write a method in that child class.

The drawback of this solution is one can only use public and protected member variables and functions in the unit test class because the test method is implemented in a child class.

Type Inference

The functions setUp() and tearDown() will be called only once in one test suite. Since the test cases are child classes in different types, we must find a way to copy value from the parent class to these child classes. This cannot be done without knowing the specific type of the test suite (Assign directly will use the copy constructor of UnitTest, which has no member variable).

Template is used to store the type of test suite and test case for two different goals. The test suite class looks like below:

class TestSuite {public:void addTest(std::string testName, TestCase *testCase) {testCases[testName] = testCase;}virtual void runTests() = 0;protected:std::map<std::string, TestCase*> testCases;};template<class UnitTestClass>class TestSuiteSpecialize : public TestSuite {public:virtual void runTests() override final {UnitTestClass base;base.setUp();for (auto testCase : testCases) {UnitTest* test = testCase.second->newTest();*dynamic_cast<UnitTestClass*>(test) = base;test->setUpEach();test->test();test->tearDownEach();}base.tearDown();}};还有不愿面对失败的尴尬。曾经怀有远大理想,拥有完美的憧憬。

The Skeleton of a C++ Testing Framework

相关文章:

你感兴趣的文章:

标签云: