5.3: The Middle Way: TDD

Test-Driven Development: Google Test

A concept you learned to love in the Software Engineering Skills course.

It’s concepts and definitions will not be repeated here, but we will introduce Google Test, a unit testing framework for C/C++ that enables us to write tests to track down bugs and reduce the amount of time needed dabbling in gdb. That is one of the major advantages of using automated test frameworks.

Google Test is a C++ (11) framework, not a C framework! We will be using g++ instead of gcc to compile everything. C++ files are suffixed with .cpp instead of .c.
Major differences between both languages exist but will not be needed to know all about in order to write a few simple tests.

Since g++ and the tool we need to build it, cmake, are not installed on the image by default, use apt install g++ cmake to download and install the toolchains.

A. Installation

Most open source libraries require you to download the source code and compile it yourself. For Google Test, we will do exactly that, since we are learning how to work with compiling and making things anyway. We want to only compile googletest, and not googlemock - both are part of the same repository.

  • Clone the github repository: https://github.com/google/googletest/. We want to build branch v1.12.x - the master branch is too unstable. Remember how to switch to that branch? Use git branch -a to see all branches, and git checkout -b [name] remotes/origin/[name] to check it out locally. Verify with git branch.
  • Create a builddir and navigate into it: mkdir build, cd build
  • Build Makefiles using Cmake: cmake ./../
  • Build binaries using make: make.

More information about CMake can be found in chapter 2.5: C Ecosystems.

If all goes according to plan, four libraries will have been created:

  1. libgtest.a
  2. ligbtest_main.a
  3. libgmock.a (we won’t use this)
  4. libgmock_main.a (we won’t use this)

In the subfolder googletest/build/lib.

B. Usage

Using the library is a matter of doing two things:

1. Adding include folders

You will need a main() function to bootstrap the framework:

// main.cpp
#include "gtest/gtest.h"

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

And another file where our tests reside:

// test.cpp
#include "gtest/gtest.h"

int add(int one, int two) {
    return one + two;
}

TEST(AddTest, ShouldAddOneAndTo) {
    EXPECT_EQ(add(1, 2), 5);
}

TEST(AddTest, ShouldAlsoBeAbleToAddNegativeValues) {
    EXPECT_EQ(add(-1, -1), -2);
}

What’s important here is the include that refers to a gtest/gtest.h file. The gtest directory resides in the include folder of your google test installation directory. That means somehow we have to educate the compiler on where to look for the includes!

The -I[directory] (I = include) flag is used to tell g++ where to look for includes.

2. Linking with the compiled libraries

When running the binary main() method, Google Test will output a report of which test passed and which test failed:

Wouters-MacBook-Air:unittest wgroenev$ ./cmake-build-debug/unittest
[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from SuiteName
[ RUN      ] SuiteName.TrueIsTrue
[       OK ] SuiteName.TrueIsTrue (0 ms)
[----------] 1 test from SuiteName (0 ms total)

[----------] 1 test from AddTest
[ RUN      ] AddTest.ShouldAddOneAndTo
/Users/wgroenev/CLionProjects/unittest/test.cpp:18: Failure
      Expected: add(1, 2)
      Which is: 3
To be equal to: 5
[  FAILED  ] AddTest.ShouldAddOneAndTo (0 ms)
[----------] 1 test from AddTest (0 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran. (0 ms total)
[  PASSED  ] 1 test.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] AddTest.ShouldAddOneAndTo

 1 FAILED TEST

However, before being able to run everything, InitGoogleTest() is implemented somewhere in the libraries we just compiled. That means we need to tell the compiler to link the Google Test libraries to our own application.

Add libraries as arguments to the compiler while linking. Remember to first use the -c flag, and afterwards link everything together.

Bringing everything together:

Wouters-MacBook-Air:debugging wgroeneveld$ g++ -I$GTEST_DIR/googletest/include -c gtest-main.cpp
Wouters-MacBook-Air:debugging wgroeneveld$ g++ -I$GTEST_DIR/googletest/include -c gtest-tests.cpp
Wouters-MacBook-Air:debugging wgroeneveld$ g++ gtest-main.o gtest-tests.o $GTEST_DIR/build/lib/libgtest.a $GTEST_DIR/build/lib/libgtest_main.a  -lpthread
Wouters-MacBook-Air:debugging wgroeneveld$ ./a.out
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from AddTest
[ RUN      ] AddTest.ShouldAddOneAndTo    

As you can see, it can be handy to create a shell variable $GTEST_DIR that points to your own Google Test directory. To do that, edit the .bashrc file in your ~ (home) folder. Remember that files starting with a dot are hidden by default, so use the -a flag of the ls command. Add the line:

export GTEST_DIR=/home/[user]/googletest/googletest

And reopen all terminals. Verify the above using echo $GTEST_DIR, it should print out the path.

If you are using a different shell, edit your shell’s config file. If you have no idea which shell you’re using, you’re probably using Bash. Verify with echo $SHELL which will likely output /bin/bash.

The -lpthread linking flag tells the compiler to link the standard threading libraries along with anything else, that are needed by GTest internally. We will get back on these in chapter 6.
Without this flag, you will get the following errors: “ld returned 1 exit status, undefined reference to pthread_[fn]”

C. ‘Debugging’ with GTest

Going back to the crackme implementation, a simplified method that verifies input is the following:

int verify(char* pwd) {
    // return 1 if verified against a pre-determined password, 0 otherwise.
}

Write a set of tests for the above method - BEFORE implementing it yourself! Time to hone your TDD skills acquired from the course ‘Software Engineering Skills’. Simply copy it into the test file, or include it from somewhere else.
You should at least have the following edge cases:

  • right password entered
  • wrong password entered
  • empty password (what about NULL or ""?)

Use the GTest macro EXPECT_TRUE and EXPECT_FALSE. These correspond to JUnit’s AssertTrue() and AssertFalse().

Again, watch out with the order in which parameters should be passed (expected/actual)! See Google Test Primer.