Cppunit is an open-source lgpl-based project. It was originally transplanted from JUnit and is an excellent open-source testing framework. Cppunit is the same as JUnit. Its main idea comes from xprogramming ). The main function is to manage unit tests and perform automated tests. This description may not allow you to experience the powerful power of the test framework. Do you encounter the following problems during development? If the answer is yes, you should learn to use this technology:
- The test code is not well maintained and discarded. It must be rewritten when testing again;
- Put too much effort into finding bugs, and new code will still have similar bugs;
- After writing the code, I have no idea whether there are a lot of bugs waiting for myself;
- The newly modified code does not know whether it affects other parts of the Code;
- The Code cannot be modified because it involves too many tasks;
...
These questions will be covered below. This powerful testing framework is not widely used by C ++ developers in China. This article introduces this framework from the perspective of developers, hoping that developers can grasp this technology at the minimum cost as soon as possible. The following describes the basic principle, cppunit principle, manual steps, general steps, and other practical problems. The following is based on cppunit1.8.0.
Back to Top
1. Basic Principles
The above problems only show that the use of cppunit is ineffective. The following describes the purpose and principles of the test, and then introduces the specific use of cppunit.
First, we need to clarify the purpose of writing test code, that is, to verify the code correctness or debug bugs. In this way, the test code is written in a targeted manner, and the test code for those that are prone to errors and changes does not need to be compiled for every detail and every function, of course, unless there are excessive energy or reliability requirements.
The relationship between coding and testing is inseparable. The recommended development process does not have to wait until all or a lot of code is compiled before testing. Instead, it completes some code, such as a function, then immediately write the test code for verification. Then write some code and test again. All previous tests are performed in each test. The advantage of this is that you have confidence in the code after writing the code and testing it again. In addition, the old code is constantly tested when writing new code, and the impact on other parts of the code can be quickly discovered and located. The process of Continuous Coding testing is the process of maintaining the test code so that the test code is always effective. With the assurance of various parts of the test code and the automatic testing mechanism, there is no concern for changing the previous code. In extreme programming (a software development idea), I even emphasized writing test code first, and then writing code that conforms to the test code to complete the entire software.
Based on the purposes and ideas mentioned above, we will summarize the unit testing principles in the development process below:
- First, write the test code, and then write the code that conforms to the test. After at least some code is completed, the corresponding test code is completed;
- The test code does not need to cover all details, but should have corresponding test cases for all main functions and possible errors;
- When a bug is detected, write the corresponding test case and debug it;
- Constantly summarize the causes of bugs and write test cases for other codes;
- Each time the Code is compiled, all previous test cases are run to verify the impact on the previous Code and eliminate this impact as soon as possible;
- Continuously maintain the test code to ensure that all tests are passed after the code changes;
With the guidance of the above theories, the test behavior can be followed. So how does cppunit implement this testing framework to help us manage test code and complete automatic testing? Next let's take a look at the principles of cppunit.
Back to Top
2. Principles of cppunit
In cppunit, one or more test cases are called fixture ). Fixture is the target to be tested. It may be an object, a group of related objects, or even a function.
With the tested fixture, you can write test code for a certain function of the fixture and a process that may fail. In this way, a complete test of a certain aspect is called testcase (test case ). The steps for writing a testcase generally include:
- Initialize fixture and other initialization operations, such as generating a group of tested objects and initializing values;
- Operate the fixture according to a function or process to be tested;
- Check whether the verification result is correct;
- Clear fixture and its resource release.
For multiple fixture test cases, part of the code (1) (4) is usually similar. cppunit introduces the setup and teardown virtual functions in many places. You can complete (1) initialization code in the setup function, and (4) code in the teardown function. You only need to complete Part of the Code (2) (3) in the specific test case function. During running, cppunit automatically Runs Setup for each test case function, and then runs teardown, in this way, there is no cross impact between test cases.
All test cases for Fixture can be encapsulated in a cppunit: testfixture subclass (the naming convention is [classname] test. Then define the setup and teardown functions of the fixture, and define a test function for each test case (the naming convention is testxxx ). The following is a simple example:
Class mathtest: Public cppunit: testfixture {protected: int m_value1, m_value2; public: mathtest () {}// initialization function void setup () {m_value1 = 2; m_value2 = 3;} // test addition function void testadd () {// step (2), operate the fixture int result = m_value1 + m_value2; // step (3): Check whether cppunit_assert (result = 5) is obtained;} // No cleanup operation is defined and no teardown is defined .} |
The success or failure of the execution result in the test function directly reflects the success and failure of the test case. Cppunit provides multiple methods for successful verification failures:
Cppunit_assert (condition) // make sure that the condition is true cppunit_assert_message (message, condition) // fails when the condition is false, and prints messagecppunit_fail (Message) // the current test fails, and print messagecppunit_assert_equal (expected, actual) // make sure the two are equal cppunit_assert_assist_message (message, expected, actual) // print cancel (expected, actual, Delta) when the two fail) // failure when the difference between expected and actual is greater than Delta |
To convert a test function of fixture into a test case, you need to generate a cppunit: testcaller object. When you finally run the test code of the entire application, you may need to run multiple test functions or even multiple fixture test cases for one fixture at the same time. Cppunit calls the set of such concurrent test cases testsuite. Testrunner runs the test case or testsuite to manage the lifecycle of all test cases. Currently, three types of testrunner are provided, including:
Cppunit: textui: testrunner // testrunner cppunit: qtui: testrunner // testrunner cppunit: mfcui: testrunner // testrunner of MFC |
The following is an example of the text mode testrunner:
Cppunit: textui: testrunner runner; cppunit: testsuite * suite = new cppunit: testsuite (); // Add a test case suite-> addtest (New cppunit :: testcaller <mathtest> ("testadd", testadd); // specify to run testsuite runner. addtest (suite); // start to run. The Test Progress and test result runner are automatically displayed. run ("", true); // run all tests and wait |
The management and display of test results involve another type of objects. They are mainly used for internal management of test results and progress, as well as display of progress and results. We will not introduce it here.
Next we will sort out our ideas and link them together with a simple example.
Back to Top
3. manual steps
First, define the test object fixture, and then determine the test case based on its functions, procedures, and previous experience. This step is very important and is directly related to the final result of the test. Of course, the process of adding test cases is a step-by-step process. After the code is completed, the function test cases are completed to ensure that the functions are completed, write test cases based on previous experience (such as boundary value test and path coverage test). Finally, complete the test case based on the bug when detecting the relevant bug.
For example, to test integer addition, first define a new testfixture subclass, mathtest, and write the test code of the test case. To add new test cases later, you only need to add new test functions and modify setup and teardown as needed. To test the new fixture, define the new testfixture subclass. Note: The following code only indicates the principle and cannot be compiled.
/// Mathtest. h // A testfixture subclass. // announce: use as your owner risk. // Author: Liqun (liqun@nsfocus.com) // data: 2003-7-5 # include "cppunit/testfixture. H "class mathtest: Public cppunit: testfixture {protected: int m_value1, m_value2; public: mathtest () {}// initialize the void setup () function (); // clear the void teardown () function; // test the added test function void testadd (); // you can add a new test function}; // mathtest. CPP // A testfixture subclass. // announce: use as your owner risk. // Author: Liqun (liqun@nsfocus.com) // data: 2003-7-5 # include "mathtest. H "# include" cppunit/testassert. H "Void mathtest: setup () {m_value1 = 2; m_value2 = 3;} void mathtest: teardown () {} void mathtest: testadd () {int result = m_value1 + m_value2; cppunit_assert (result = 5 );} |
Then write the main function, organize the test cases to be tested into testsuite, and then run them through testruner. This part of code does not need to be changed much when new test cases are added later. You only need to add a new test case to testsuite.
/// Main. CPP // main file for cppunit test. // announce: use as your owner risk. // Author: Liqun (liqun@nsfocus.com) // data: 2003-7-5 // Note: cannot compile, only for study. # include "mathtest. H "# include" cppunit/UI/text/testrunner. H "# include" cppunit/testcaller. H "# include" cppunit/testsuite. H "int main () {cppunit: textui: testrunner runner; cppunit: testsuite * suite = new cppunit: testsuite (); // Add a test case suite-> addtest (New cppunit: testcaller <mathtest> ("testadd", testadd); // specify the runner for running testsuite. addtest (suite); // start to run. The Test Progress and test result runner are automatically displayed. run ("", true); // run all tests and wait} |
Back to Top
4. Common usage
According to the above method, to add a new test case, you need to add each test case to testsuite, and add all header files to main. CPP. Cppunit provides cppunit: testsuitebuilder, cppunit: testfactoryregistry, and a bunch of macros to conveniently register testfixture and test cases to testsuite. The following is the common usage:
/// Mathtest. h // A testfixture subclass. // announce: use as your owner risk. // Author: Liqun (liqun@nsfocus.com) // data: 2003-7-5 # include "cppunit/extensions/helpermacros. H "class mathtest: Public cppunit: testfixture {// declare a testsuite cppunit_test_suite (mathtest); // Add the test case to testsuite, to define a new test case, you must declare cppunit_test (testadd) here; // testsuite declares cppunit_test_suite_end (); // The rest remain unchanged. Protected: int m_value1, m_value2; public: mathtest () {} // initialize the void setup () function; // clear the void teardown () function; // test the added test function void testadd (); // you can add a new test function}; // mathtest. CPP // A testfixture subclass. // announce: use as your owner risk. // Author: Liqun (liqun@nsfocus.com) // data: 2003-7-5 # include "mathtest. H "// register this testsuite to the testsuite named" alltest ". If it is not defined, it will be automatically defined // You can also cppunit_test_suite_registration (mathtest ); register to a globally named testsuite. cppunit_test_suite_named_registration (mathtest, "alltest"); // The following void mathtest: setup () {m_value1 = 2; m_value2 = 3;} void mathtest: teardown () {} void mathtest: testadd () {int result = m_value1 + m_value2; cppunit_assert (result = 5);} // main. CPP // main file for cppunit test. // announce: use as your owner risk. // compile: G ++-lcppunit mathtest. CPP main. CPP // run :. /. out // test: RedHat 8.0 cppunit1.8.0 // Author: Liqun (A litthle modification. liqun@nsfocus.com) // data: 2003-7-5 // no need to include the header file of all testfixture subclasses # include <cppunit/extensions/testfactoryregistry. h> # include <cppunit/UI/text/testrunner. h> // If testsuite is not changed, this file does not need to be changed later. int main () {cppunit: textui: testrunner runner; // obtain the specific testsuite from the registered testsuite. No parameter is provided to obtain the unnamed testsuite. cppunit: testfactoryregistry & registry = cppunit: testfactoryregistry: getregistry ("alltest"); // Add this testsuite to the runner of testrunner. addtest (registry. maketest (); // run the test runner. run ();} |
To add a new test case, you only need to declare it at the beginning of the class definition.
Back to Top
5. Other practical problems
Generally, the code that contains the test case and the tested object are in different projects. You should write testfixture in another project (preferably in different directories) and include the tested objects in the test project.
When testing a class or function, this testfixture may reference other classes or functions. to isolate the impact of other code, you should temporarily define some program in the source file, simulate these classes or functions. The code can be valid in the test project through macro definition, but not in the tested project.