1. Home
  2. Tutorials
  3. C/C++
  4. GTest
Yolinux.com Tutorial

GoogleTest: C++ unit test framework

GoogleTest is a C++ unit test framework for classes. GoogleTest has integrated support for many continuous integration systems like Jenkins.



Free Information Technology Magazines and Document Downloads
TradePub link image

GoogleTest Installation:

This tutorial assumes that you have a C++ compiler and build tools installed on your system.

Download GoogleTest: gtest-1.7.0.zip

GoogleTest:

Installation: install to /opt/gtest/...
unzip gtest-1.7.0.zip
cd gtest-1.7.0
sudo mkdir /opt/gtest /opt/gtest/include /opt/gtest/lib
./configure --prefix=/opt/gtest
make
sudo cp -a include/gtest/ /opt/gtest/include
sudo cp -a lib/.libs/* /opt/gtest/lib
rm /opt/gtest/lib/libgtest.la
rm /opt/gtest/lib/libgtest_main.la
sudo cp -a lib/libgtest.la /opt/gtest/lib
sudo cp -a lib/libgtest_main.la /opt/gtest/lib
Note: cp flag "-a" preserves symbolic links

You can also locate GoogleTest in global user directories /use/include and /usr/lib. I personally don't mix package controlled system software with non-package controlled software and thus I use /opt/gtest and not the system areas.

GoogleTest Example:

Google Test is a framework in which we write a unit test driver to call and test C++ class methods.

Example C++ Classes to Test:

The C++ classes which will be tested by Google Test. The total application will not be tested although it is listed.

File: src/Addition.hpp
#ifndef _ADDITION_HPP_
#define _ADDITION_HPP_
class Addition 
{
public:
    static int twoValues(const int x, const int y);   
};
#endif

File: src/Addition.cpp
#include "Addition.h"

int 
Addition::twoValues(const int x, const int y)
{
    return x + y;
}

File: src/Multiply.hpp
#ifndef _MULTIPLY_HPP_
#define _MULTIPLY_HPP_
class Multiply 
{
public:
    static int twoValues(const int x, const int y);   
};
#endif

File: src/Multiply.cpp
#include "Multiply.h"

int
Multiply::twoValues(const int x, const int y)
{
    return x * y;
}

File: src/ExampleApp.cpp
#include "Addition.hpp"
#include "Multiply.hpp"

#include <stdio.h>

int main()
{
    int x = 4;
    int y = 5;

    int z1 = Addition::twoValues(x,y);
    printf("\nAddition Result: %d\n", z1);

    int z2 = Multiply::twoValues(x,y);
    printf("Multiply Result: %d\n", z2);

    delete corporation;
    return 0;
}

File: src/Makefile
CXX = g++
CXXFLAGS = -g
INCS = -I./
OBJS = Addition.o Multiply.o

exampleapp: ExampleApp.cpp $(OBJS)
	$(CXX) $(CXXFLAGS) -o exampleapp ExampleApp.cpp $(OBJS)

.cpp.o:
	$(CXX) $(CXXFLAGS) -c $< -o $@ $(INCS)

clean:
	rm *.o exampleapp gmon.out

Compile: make
Run: exampleapp

GoogleTest Unit Test Source:
  • Program main() to specify tests to run: Main_TestAll.cpp
  • Classes to perform the unit tests:
    • Addition_Test.cpp
    • Multiply_Test.cpp

The GoogleTest framework uses macros to define tests and apply tests:

GoogleTest support tests (TEST(class,test_name)) and test frameworks (TEST_F(class,test_name)). This tutorial will use the more extensive and complete test framework. The framework employs a user written test class derived from ::testing::Test which supplies SetUp() and TearDown() functions.

Tests:
  • ASSERT_XXX(): If assertion fails then processing of test terminate.
  • EXPECT_XXX(): nonfatal failure, allowing processing to continue.

TestFatalNonFatal
TrueASSERT_TRUE(condition)EXPECT_TRUE(condition)
FalseASSERT_FALSE(condition)EXPECT_FALSE(condition)
EqualASSERT_EQ(arg1,arg2)EXPECT_EQ(arg1,arg2)
Not EqualASSERT_NE(arg1,arg2)EXPECT_NE(arg1,arg2)
Less ThanASSERT_LT(arg1,arg2)EXPECT_LT(arg1,arg2)
Less Than or EqualASSERT_LE(arg1,arg2)EXPECT_LE(arg1,arg2)
Greater ThanASSERT_GT(arg1,arg2)EXPECT_GT(arg1,arg2)
Greater Than or EqualASSERT_GE(arg1,arg2)EXPECT_GE(arg1,arg2)
C String EqualASSERT_STREQ(str1,str2)EXPECT_STREQ(str1,str2)
C String Not EqualASSERT_STRNE(str1,str2)EXPECT_STRNE(str1,str2)
C String Case EqualASSERT_STRCASEEQ(str1,str2)EXPECT_STRCASEEQ(str1,str2)
C String Case Not EqualASSERT_STRCASENE(str1,str2)EXPECT_STRCASENE(str1,str2)
Verify that exception is thrownASSERT_THROW(statement,exception_type)EXPECT_THROW(statement,exception_type)
Verify that exception is thrownASSERT_ANY_THROW(statement)EXPECT_ANY_THROW(statement)
Verify that exception is NOT thrownASSERT_NO_THROW(statement)EXPECT_NO_THROW(statement)
See the GoogleTest advanced guide for more "EXPECT" and "ASSERT" options.

Unit Test Code:

File: test/src/Main_TestAll.cpp
#include <limits.h>
#include "gtest/gtest.h"

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

File: test/src/Addition_Test.cpp
#include <limits.h>
#include "gtest/gtest.h"
#include "Addition.h"

class AdditionTest : public ::testing::Test {
 protected:
  virtual void SetUp() {
  }

  virtual void TearDown() {
    // Code here will be called immediately after each test
    // (right before the destructor).
  }
};

TEST_F(AdditionTest,twoValues){
    const int x = 4;
    const int y = 5;
    Addition addition;
    EXPECT_EQ(9,addition.twoValues(x,y));
    EXPECT_EQ(5,addition.twoValues(2,3));
}

File: test/src/Multiply_Test.cpp
#include <limits.h>
#include "gtest/gtest.h"
#include "Multiply.h"

class MultiplyTest : public ::testing::Test {
 protected:
  virtual void SetUp() {
  }

  virtual void TearDown() {
  }
};

TEST_F(MultiplyTest,twoValues){
    const int x = 4;
    const int y = 5;
    Multiply multiply;
    EXPECT_EQ(20,multiply.twoValues(x,y));
    EXPECT_EQ(6,multiply.twoValues(2,3));
}

File: test/src/Makefile
CXX = g++
CXXFLAGS = -g -L/opt/gtest/lib -lgtest -lgtest_main -lpthread
INCS = -I./ -I../../src -I/opt/gtest/include
OBJS = ../../src/Addition.o Addition_Test.o ../../src/Multiply.o Multiply_Test.o

testAll: $(OBJS)
	$(CXX) $(CXXFLAGS) $(INCS) -o testAll  Main_TestAll.cpp $(OBJS)

.cpp.o:
	$(CXX) $(CXXFLAGS) -c $< -o $@ $(INCS)

clean:
	rm testAll *.o testAll.xml

Compile: make
Run: testAll --gtest_output="xml:./testAll.xml"

[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from MultiplyTest
[ RUN      ] MultiplyTest.twoValues
[       OK ] MultiplyTest.twoValues (5 ms)
[----------] 1 test from MultiplyTest (5 ms total)

[----------] 1 test from AdditionTest
[ RUN      ] AdditionTest.twoValues
[       OK ] AdditionTest.twoValues (4 ms)
[----------] 1 test from AdditionTest (4 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran. (9 ms total)
[  PASSED  ] 2 tests.


GoogleTest XML reporting:

The following is the XML output from GoogleTest in JUnit format: testAll --gtest_output="xml:./testAll.xml"

Output File: test/src/testAll.xml
<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="2" failures="0" disabled="0" errors="0" timestamp="2014-09-29T19:16:59" time="0" name="AllTests">
  <testsuite name="MultiplyTest" tests="1" failures="0" disabled="0" errors="0" time="0">
    <testcase name="twoValues" status="run" time="0" classname="MultiplyTest" />
  </testsuite>
  <testsuite name="AdditionTest" tests="1" failures="0" disabled="0" errors="0" time="0">
    <testcase name="twoValues" status="run" time="0" classname="AdditionTest" />
  </testsuite>
</testsuites>
The native Jenkins JUnit XML interpreter can interpret this XML to present test results plots.

Continuous Integration with Jenkins:

Jenkins Ant file to compile and run a GoogleTest test:

File: ./build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="CppApp" default="jenkins" basedir=".">
    <description>
        Jenkins Ant file for CppApp
    </description>
  <!-- set global properties for this build -->
  <property name="make.cmd" value="/usr/bin/make"/>
  <property name="build.native" value="./src"/>
  <property name="build.test" value="./test/src"/>

  <target name="init">
    <!-- Create the time stamp -->
    <tstamp/>
  </target>

  <target name="compile" description="compile the source" >
    <exec dir="${build.native}" executable="${make.cmd}" failonerror="true">
    </exec>
  </target>

  <arget name="compile-test" description="compile the source" depends="compile">
    <exec dir="${build.test}" executable="${make.cmd}" failonerror="true">
    </exec>
  </target>

  <target name="clean" description="clean up" >
    <exec dir="${build.native}" executable="${make.cmd}" failonerror="false">
      <arg value="clean"/>
    </exec>
    <exec dir="${build.test}" executable="${make.cmd}" failonerror="false">
      <arg value="clean"/>
    </exec>
  </target>

  <target name="run" description="Run application" depends="compile">
    <exec dir="${build.native}" executable="exampleapp" failonerror="true">
    </exec>
  </target>

  <target name="run-test" description="Run application" depends="compile-test">
    <exec dir="${build.test}" executable="testAll" failonerror="true">
      <arg line="--gtest_output="xml:./testAll.xml""/>
    </exec>
  </target>

</project>

Building and running unit tests:

  • Build: ant
  • Test: ant run-test

GoogleTest presents its unit test output in JUnit format, a native format for Jenkins unit test reporting. The following is Jenkins configuration to plot JUnit results:

Jenkins Global Configuration:

Jenkins -> Manage Jenkins -> Configure System:
Jenkins/Hudson JUnit configuration

Add the following for GoogleTest support:
  • LD_LIBRARY_PATH: /opt/gtest/lib
  • PATH: ./

[Potential Pitfall]: It the Jenkins PATH is not set to include the current working directory "./", you will get the error:

Execute failed: java.io.IOException: Cannot run program
error=2, No such file or directory

The user paths should be set as well:

File: ~/.bashrc (snipet)
...
...

if [ -d /opt/gtest/ ]
then
  export GTEST_HOME=/opt/gtest
  export LD_LIBRARY_PATH=$GTEST_HOME/lib:$LD_LIBRARY_PATH
fi

...
...

Jenkins Project Configuration:

Jenkins/Hudson JUnit configuration
Specify where the XML run results will be located.

Jenkins unit test reporting:

Jenkins/Hudson JUnit reporting
Note that there were two tests thus the count is two. Each test had multiple "EXPECT" statements but there were all part of the same test.

For more, see the YoLinux.com Tutorials Jenkins installation and configuration

Links:

Books:

C++ How to Program
by Harvey M. Deitel, Paul J. Deitel
ISBN #0131857576, Prentice Hall

Fifth edition. The first edition of this book (and Professor Sheely at UTA) taught me to program C++. It is complete and covers all the nuances of the C++ language. It also has good code examples. Good for both learning and reference.

Amazon.com
"Advanced UNIX Programming" Second Edition
by Marc J. Rochkind
ISBN # 0131411543, Addison-Wesley Professional Computing Series

Amazon.com
"Advanced Programming in the UNIX Environment" First Edition
by W. Richard Stevens
ISBN # 0201563177, Addison-Wesley Professional Computing Series

It is the C programmers guide to programming on the UNIX platform. This book is a must for any serious UNIX/Linux programmer. It covers all of the essential UNIX/Linux API's and techniques. This book starts where the basic C programming book leaves off. Great example code. This book travels with me to every job I go to.

Amazon.com
"UNIX Network Programming, Volume 1: Networking APIs - Sockets and XTI" Second Edition
by W. Richard Stevens
ISBN # 013490012X, Prentice Hall PTR

This book covers network APIs, sockets + XTI, multicast, UDP, TCP, ICMP, raw sockets, SNMP, MBONE. In depth coverage of topics.

Amazon.com
"UNIX Network Programming Volume 2: Interprocess Communications"
by W. Richard Stevens
ISBN # 0130810819, Prentice Hall PTR

This book covers semaphores, threads, record locking, memory mapped I/O, message queues, RPC's, etc.

Amazon.com
"Advanced Unix Programming"
by Warren W. Gay
ISBN # 067231990X, Sams White Book Series

This book covers all topics in general: files, directories, date/time, libraries, pipes, IPC, semaphores, shared memory, forked processes and I/O scheduling. The coverage is not as in depth as the previous two books (Stevens Vol 1 and 2)

Amazon.com

   

    Bookmark and Share

Advertisements