c and unity.pdf

Upload: kukuduxu

Post on 04-Jun-2018

221 views

Category:

Documents


0 download

TRANSCRIPT

  • 8/13/2019 c and unity.pdf

    1/15

    Unit Testing C++ Code CppUnit by ExampleVenkat Subramaniam

    [email protected]://www.agiledeveloper.com/download.aspx

    AbstractJUnit for Java popularized unit testing and developers using different languages arebenefiting from appropriate tools to help with unit testing. In this article, I showusingexampleshow to create unit tests for your C++ applications.

    Why sudden interest in C++?I have been asked at least three times in the past few weeks Unit testing is cool, but howdo I do that in my C++ application? I figured, if my clients are interested in it, thereshould be general wider interest out there in that topic and so decided to write this article.

    Assuming your familiarity with Unit TestingIn this article, I assume you are familiar with unit testing. There are some great books toread on that topic (Ive listed my favorites1, 2 in the references section). You may alsorefer to my article on Unit Testing3.

    How is Unit Testing different in C++?Java and C# (VB.NET) have a feature that C++ does not reflection. If you have usedJUnit4or NUnit5, you know how (easy it is) to write a test case. You either derived froma TestCase class in the case of Java or you mark your class with a TestFixture attribute inthe case of .NET (With Java 5s annotation, you may also look forward, in JUnit 4.0, tomarking your Java test case instead of extending TestCase). Using reflection the unittesting tool (JUnit/NUnit) finds your test methods dynamically.

    Since C++ does not have support for reflection, it becomes a bit of a challenge to write aunit test in C++, at least the JUnit way. You will have to exploit some of the traditionalfeatures of C++ to get around the lack of reflection feature.

    How to tell what your tests are?In C++, how can you make the program know of the presence of a certain objectdynamically or automatically? You can do that by registering an instance of your classwith a static or global collection. Lets understand this with an example. If you are anexpert in C++, browse through this section. If you will benefit from this example, thenread it carefully and try it out for yourself.

    Lets assume we have different types of Equipment: Equipment1, Equipment2, etc. MoreEquipment types may be added or some may be removed. We want to find what kind ofequipment is available for us to use. The following code does just that. Lets take a lookat it step by step.

    #i ncl ude usi ng namespace std;

  • 8/13/2019 c and unity.pdf

    2/15

    cl ass Equi pment{publ i c:

    vi r t ual str i ng i nf o( ) = 0;};

    Equi pment is defined an abstract base class with a pure virtual function i nf o( ) .

    #i ncl ude "Equi pment . h"#i ncl ude

    cl ass Equi pment Col l ect i on{publ i c:

    stat i c voi d add( Equi pment * pEqui pment ){

    i f ( pEqui pment Li st == NULL){

    pEqui pment Li st = new vect or( ) ;

    }

    pEqui pment Li st - >push_back(pEqui pment ) ;}

    ~Equi pment Col l ect i on( ){

    del et e pEqui pment Li st ;}

    stat i c vect or & get ( ){

    return *pEqui pment Li st ;}

    pr i vat e:stat i c vect or * pEqui pment Li st ;

    };

    Equi pment Col l ect i onholds a collection (vect or ) of Equipment through a static pointerpEqui pment Li st . This static pointer is assigned to an instance of vect or ,an instance of vect or of Equi pment pointers, the first time the add() method is called(the given code is not thread-safe). In the add() method, we add the pointer to the givenEqui pment to the collection. The collection is initialized in the EquipmentCollection.cppfile as shown below:

    / / Equi pment Col l ect i on. cpp

    #i ncl ude "Equi pment Col l ect i on. h"

    vect or* Equi pment Col l ect i on: : pEqui pment Li st = NULL;

    How are elements placed into this collection? I use a class Equi pmentHel per to do this:

    #i ncl ude "Equi pment Col l ect i on. h"

    cl ass Equi pment Hel per

  • 8/13/2019 c and unity.pdf

    3/15

    {publ i c:

    Equi pment Hel per ( Equi pment * pEqui pment ){

    Equi pment Col l ect i on: : add( pEqui pment ) ;}

    };

    The constructor of Equi pmentHel per receives Equi pment and adds it to the collection.

    Equi pment 1is shown below:

    #i ncl ude " Equi pment Hel per . h"

    cl ass Equi pment 1 : publ i c Equi pment{publ i c:

    vi r t ual s t r i ng i nf o( ){

    return "Equi pment 1";}

    pr i vat e:stat i c Equi pment Hel per hel per ;

    };

    The class holds a static instance of Equi pmentHel per . The static instance is initialized asshown below in the Equipment1.cpp file:

    / / Equi pment 1. cpp#i ncl ude "Equi pment 1. h"

    Equi pment Hel per Equi pment 1: : hel per ( new Equi pment 1( ) ) ;

    The hel per is provided with an instance of Equi pment 1 which it registers with thecollection in Equi pment Col l ect i on. Similarly, I have another class Equi pment 2with astatic member variable named hel per of type Equi pmentHel per and it is given aninstance of Equi pment 2 (I have not shown the code as it is similar to the code forEqui pment 1). You may write other classes (like Equipment3, Equipment4, etc.)similarly.

    Lets take a closer look at whats going on. When a C++ program starts up, before thecode in mai n( ) executes, all static members of all the classes are initialized. The order in

    which these are initialized is, however, unpredictable. The members are initialized to 0unless some other value is specified. In the example code above, before main starts, thehelper within Equiment1, Equipment2, etc. are all initialized, and as a result, thecollection of Equi pment is setup with an instance of these classes. Now, lets take a lookat the mai n( ) function.

    #i ncl ude "Equi pment Col l ect i on. h"#i ncl ude usi ng namespace std;

  • 8/13/2019 c and unity.pdf

    4/15

    i nt mai n( i nt ar gc, char * argv[]){

    cout

  • 8/13/2019 c and unity.pdf

    5/15

    Now, I can use these macros to write a class Equi pment 3as shown below:#i ncl ude " Equi pment Hel per . h"

    cl ass Equi pment 3 : publ i c Equi pment{

    publ i c:vi r t ual s t r i ng i nf o( ){

    return "Equi pment 3";}

    DECLARE_EQUIPMENT()

    };

    / / Equi pment 3. cpp#i ncl ude "Equi pment 3. h"

    DEFINE_EQUIPMENT(Equipment3)Of course we did not make any change to mai n( ) . The output from the program is shownbelow:

    Li st cont ai nsEqui pment 1Equi pment 2Equi pment 3

    There is no guarantee that the program will produce the output in this nice order. Youmay get a different ordering from whats shown above.

    OK, lets move on to what we are here for, to see how to write unit tests with CPPUnit.

    Setting up CPPUnitI am using CPPUnit 1.10.26 in this example. Download cppunit-1.10.2.tar.gz anduncompress it on your system. On mine, I have uncompressed it in c:\programs. OpenCppUnitLibraries.dsw which is located in cppunit-1.10.2\src directory and compile it. Ihad to build it twice to get a clean compile. After successful build, you should seecppunit-1.10.2\lib directory.

    Setting up Your ProjectWe will use Visual Studio 2003 to build this example unmanaged C++. Created a

    console application named MyApp. In Solution Explorer, right click on Source Filesand select Add | Add New Item. Create a file named Main.cpp as shown here:

  • 8/13/2019 c and unity.pdf

    6/15

    Write a dummy mai n( ) method for now:

    i nt mai n( i nt ar gc, char * argv[]){

    r et ur n 0;}

    Right click on the project in Solutions Explorer and select Properties. For the AdditionalInclude Directories for C/C++ properties, enter the directory where you have CPPUnitinstalled on your machine. I have it under c:\programs directory. So, I entered thefollowing:

  • 8/13/2019 c and unity.pdf

    7/15

    Modify the Runtime Library in Code Generation as shown below:

  • 8/13/2019 c and unity.pdf

    8/15

    Set the Enable Run-Time Type Info in Language properties as shown below:

    Two more steps before we can write code. Under Linker, for General properties, setAdditional Library Directories to point to the lib directory under CPPUnit as shownbelow:

  • 8/13/2019 c and unity.pdf

    9/15

    Finally, set the Additional Dependencies in Input as shown below:

    Running your unit testLets now create a Unit Test. We will start in Main.cpp that we have created in the last

    section. Modify that file as shown below:

    1: #i ncl ude 2: #i ncl ude 3: #i ncl ude 4: 5: usi ng namespace CppUni t ;6: 7: i nt mai n( i nt ar gc, char * argv[])8: {9: CppUni t : : Test * sui t e =10: CppUni t : : Test Fact or yRegi st r y: : get Regi st r y( ) . makeTest ( ) ;11: 12: CppUni t : : TextUi : : Test Runner r unner ;13: r unner . addTest ( sui t e ) ;14: 15: r unner . set Out put t er ( new CppUni t : : Compi l er Out put t er (16: &runner . r esul t ( ) , s td: : cerr ) ) ;17: 18: ret urn r unner . r un( ) ? 0 : 1;19: }

  • 8/13/2019 c and unity.pdf

    10/15

    In lines 1 to 3, we have included the necessary header files from CPPUnit. TheCompi l erOut put t er class is useful to display the results from the run of a test. The

    Test Fact or yRegi st r y class keeps a collection of tests (this is like theEqui pment Col l ect i on class we saw in our example). The Test Runner class willexecute each test from the test classes we register with theTest Fact or yRegi st r y. This

    is like the code that iterated though the Equi pment collection in our example. In thatexample, we iterated and printed the result of i nf o( ) method invocation. Test Runner does something useful runs your tests.

    In lines 9 and 10, I invoke the Test Fact or yRegi st r ys get Regi st r y( ) method andinvoke its makeTest ( ) method. This call will return all the tests found in your application.In lines 12 through 16, I have created aTest Runner object, the one that will take care ofrunning the tests and reporting the output using the Compi l erOutput t er . TheCompi l erOut put t er will format the error messages in the compiler compatible format the same format as the compiler errors so you can easily identify the problem areas andalso this facilitates IDEs to jump to the location of the error in your test run.

    Finally in line 18, I ask the tests to run. We havent written any tests yet. That is OK, letsgo ahead and give this a try. Go ahead, compile the code and run it. You should get thefollowing output:

    OK ( 0)

    This says that all is well (after all there is not a whole lot we have done to mess up!) andthat no tests were found.

    Writing unit testsWe will take the test first driven approach1,3to writing our code. We will build a simplecalculator with two methods, add() and di vi de( ) . Lets start by creating a Test class asshown below:

    / / Cal cul at or Test. h

    #i ncl ude

    usi ng namespace CppUni t ;

    cl ass Cal cul at or Test : publ i c Test Fi xture{

    CPPUNI T_TEST_SUI TE( Cal cul at orTest ) ;CPPUNI T_TEST_SUI TE_END( ) ;

    };

    / / Cal cul at or Test . cpp

    #i ncl ude "Cal cul at or Test . h"

    CPPUNI T_TEST_SUI TE_REGI STRATI ON( Cal cul atorTest ) ;

  • 8/13/2019 c and unity.pdf

    11/15

    You may study the macros I have used in the header file. I will expand here on the macroI have used in the cpp file, that is CPPUNI T_TEST_SUI TE_REGI STRATI ON:

    #def i ne CPPUNI T_TEST_SUI TE_REGI STRATI ON( ATest Fi xt ureType ) \stat i c CPPUNI T_NS: : AutoRegi st erSui t e< ATest Fi xt ur eType > \

    CPPUNI T_MAKE_UNI QUE_NAME( aut oRegi st erRegi st r y__ )

    Notice the creation of a static object. This object will take care of registering the testfixture object with the test registry.

    Now, lets go ahead and write a test:

    / / Cal cul at or Test. h

    #i ncl ude

    usi ng namespace CppUni t ;

    cl ass Cal cul at or Test : publ i c Test Fi xture{

    CPPUNI T_TEST_SUI TE( Cal cul atorTest

    CPPUNIT_TEST(testAdd);) ;

    CPPUNI T_TEST_SUI TE_END( ) ;

    public:

    voidtestAdd()

    {

    }};

    I have created a method t est Add( ) and have declared a macro CPPUNI T_TEST tointroduce that method as a test. Lets compile and execute this code. The output is shownbelow:

    .

    OK ( 1)

    The above output shows that one test was executed successfully.

    Lets modify the test code to use the Calculator:

    voi d testAdd(){

    Calculator calc;

    CPPUNIT_ASSERT_EQUAL(5, calc.add(2, 3));

    }

    I am using the CPPUNI T_ASSERT_EQUALto assert that the method add returns the expectedvalue of 5. Lets create the Calculator class and write the add() method as shown below:

  • 8/13/2019 c and unity.pdf

    12/15

    #pr agma once

    cl ass Cal cul at or{publ i c:

    Cal cul at or ( voi d) ;vi r t ual ~Cal cul at or( voi d) ;

    i nt add( i nt op1, i nt op2){

    return 0;}

    };

    Lets execute the test and see what the output is:

    . F

    c: \ . . . \ cal cul at ort est . h( 20) : err or : Asser t i onTest name: Cal cul at or Test : : t est Addequal i t y asser t i on f ai l ed- Expect ed: 5- Actual : 0

    Fai l ures ! ! !Run: 1 Fai l ur e t ot al : 1 Fai l ur es: 1 Er r or s: 0

    The output shows us that my test failed (obviously as I am returning a 0 in the add() method). Lets fix the code now to return op1 + op2. The output after the fix is:

    .

    OK ( 1)

    Lets write a test for the di vi de( ) method as shown below:

    voi d t estDi vi de( ){

    Cal cul at or cal c;

    CPPUNI T_ASSERT_EQUAL( 2. 0, cal c. di vi de( 6, 3) ) ;}

    Remember to add

    CPPUNI T_TEST( t est Di vi de) ;

    as well.

    Now the di vi de( ) method in the Cal cul at or class looks like this:

  • 8/13/2019 c and unity.pdf

    13/15

    doubl e di vi de( i nt op1, i nt op2){

    return op1/ op2;}

    The output is shown below:

    . .

    OK ( 2)

    It shows us that two tests were executed successfully.

    Lets write one more test, this time a negative test:

    voi d t est Di vi deByZer o( ){

    Cal cul at or cal c;

    cal c. di vi de( 6, 0) ;}

    We are testing for division by zero. Intentionally I have not added any asserts yet. If werun this, we get:

    . . . E

    ##Fai l ure Locat i on unknown## : Er r orTest name: Cal cul at or Test : : t est Di vi deByZer ouncaught except i on of unknown type

    Fai l ures ! ! !Run: 3 Fai l ur e t ot al : 1 Fai l ur es: 0 Er r or s: 1

    CppUnit tells us that there was an exception from the code. Indeed, we should get anexception. But the success of this test is the method divide throwing the exception. So,the test must have reported a success and not a failure. How can we do that? This iswhere CPP_FAI Lcomes in:

    voi d t est Di vi deByZer o( ){

    Cal cul at or cal c;

    t ry{cal c. di vi de( 6, 0) ;CPPUNI T_FAI L("Expect ed di vi si on by zer o t o throw except i on! ! ! ! ") ;

    }cat ch( Except i on ex){

    / / Fai l t hr ows Except i on,/ / we wi l l t hr ow i n back to CppUni t t o handl e

  • 8/13/2019 c and unity.pdf

    14/15

    throw;}cat ch( . . . ){

    / / Di vi si on by Zer o i n t hi s case. Good.}

    }

    If the method di vi de( ) throws an exception, then we are good. If the method does notthrow exception, then we force fail this test by call to the CPPUNI T_FAI L.

    Lets run the test now:

    . . .

    OK ( 3)

    We have passing tests.

    Reexamining the Divide by ZeroLets make a change to the di vi de( ) method in Calculatorwe will make the methodtake doubl einstead of i nt as parameters.

    doubl e di vi de( double op1, double op2){

    return op1/ op2;}

    Lets rerun out tests and see what we get:

    . . . F

    c: \ agi l i t y\ myapp\ cal cul at or t est. h( 39) : er r or : Asser t i onTest name: Cal cul at or Test : : t est Di vi deByZer of or ced f ai l ur e- Expect ed di vi si on by zer o to t hr ow except i on! ! ! !

    Fai l ures ! ! !Run: 3 Fai l ur e t ot al : 1 Fai l ur es: 1 Er r or s: 0

    CppUnit tells us that the division by zero did not throw the exception as expected! Thereason is the behavior of floating point division is different from the integer division.While integer division by zero throws an exception, division by zero for double returnsinfinity. This further illustrates how CPPUNI T_FAI Lhelps.

    ConclusionIf you are a C++ developer, you can take advantage of unit testing using CppUnit. SinceC++ does not provide some features of Java and .NET languages, you have to workaround those limitations. However, CppUnit addresses these by providing helper macros.We first took time to see how in C++ we can identify classes automatically. Then wedelved into examples of using CppUnit.

  • 8/13/2019 c and unity.pdf

    15/15

    References

    1. Test Driven Development: By Example, Kent Beck.2. Pragmatic Unit Testing in C# with NUnit, Andy Hunt, Dave Thomas.3.

    Test Driven Development Part I: TFC,http://www.agiledeveloper.com/download.aspx

    4. http://www.junit.org5. http://www.sourceforge.net/projects/nunit6. http://www.sourceforge.net/projects/cppunit

    http://www.agiledeveloper.com/download.aspxhttp://www.junit.org/http://www.sourceforge.net/projects/nunithttp://www.sourceforge.net/projects/cppunithttp://www.sourceforge.net/projects/cppunithttp://www.sourceforge.net/projects/nunithttp://www.junit.org/http://www.agiledeveloper.com/download.aspx