Tips & Tricks

Unit Testing C++ with Google Test

Unit testing C++ applications is not exactly easy. Without any embeddable metadata, the actual process of running a unit test has to be defined explicitly in code. Luckily, frameworks such as Google Test provide comprehensive support for this. Let’s take a look.

Download and Build

Google Test is not header-only: there are libraries to build. So, as a Visual Studio user, you have essentially two options.

Option 1 (probably the easiest): Just install Google Test from Nuget:

This sets everything up, but your mileage may vary: the package comes with pre-build binaries that may not target the compiler and bitness you want. So the other option is…

Option 2 (more effort required): Download and build Google Test in some local directory (e.g., c:\gtest). Now, lucky for us, Google Test comes with a set of Visual Studio projects/solutions ready for building the binaries. You’ll find those in \msvc. Now is a chance for you to open up the solution (e.g., gtest.sln), configure things how you want, then build it and copy the library files and EXEs to some convenient location (e.g., \gtest\lib).

The icing on the cake is you can now open up the %LOCALAPPDATA%\Microsoft\MSBuild\v4.0 folder and edit the .user.props files to always include the Google Test paths. Or use Visual Studio’s own Property Manager. This way you’ll always have the directories available, though the library to link against depends on whether you’re building a Debug or Release version.

How It Works

Let’s state the obvious first: Google Test requires you to build an executable. It’s a command-line app, and when you run it, it runs your tests. Even makes them nice and colorful:

So, it is entirely possible to keep rebuilding and running the EXEs, visually inspect the results and fix when necessary. Of course, this works when you have a few tests: if you have hundreds, well… this is where you need tool support.

At any rate, the first thing you need to do is

  • Add the include header
    #include <gtest/gtest.h>
  • Reference the appropriate libraries. If you are using the NuGet package, you don’t have to do this, but otherwise you need to reference gtest.lib for the Release build and gtestd.lib for Debug.

So, in order to run the tests, Google Test needs you to implement the main entrypoint with code similar to:

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

The call to InitGoogleTest() parses command-line arguments: you can specify your own arguments. To find out which arguments Google Test supports, just run your EXE with the --help flag.

What is a Test?

In its simplest form, a test is just a chunk of code that checks if some condition holds. If it holds, the test passes; if it doesn’t, the test fails.

There are many ways to test conditions. The simplest varieties are EXPECT_TRUE() and EXPECT_FALSE, which can be used as follows:

TEST(Addition, CanAddTwoNumbers)
{
  EXPECT_TRUE(add(2, 2) == 4);
}

You’ll notice that the above expectation is wrapped in a TEST() macro. This macro is necessary to tell Google Test that you are, in fact, writing a test. A single test can contain more than once statement, of course.

A slightly more useful variety is EXPECT_EQ(), which tests that the second argument is the same as the first, for example

EXPECT_EQ(4, add(2,2)) << "Two plus two must equal four";

So the above states that 4 should be the result of adding 2 and 2. I also use the << operator to give a human-readable message to whoever is running the test in case it fails.

Now, EXPECT_EQ does not stop execution. If you have several EXPECT_EQ calls in one test, they will all run. If you want the first test comparison to stop execution if it fails, use ASSERT_EQ instead.

Some refinements of EXPECT_EQ (and ASSERT_, by analogy) include comparisons (e.g., EXPECT_LT is a less-than check), comparisons of floating-point numbers (EXPECT_FLOAT_EQ and similar), and many more.

At any rate, your TEST() is now runnable. Its location isn’t important so long as it ends up being part of the build. If you want to figure out what tests you’ve got written, just use R++’s Unit Test Explorer, which will locate and list all the unit tests in the project.

Test Fixtures

Whenever you’re testing stuff, you are probably testing a class or set of classes. Setting up and destroying them on every test might be painful and this is what test fixtures are for.

A test fixture is a class that inherits from ::testing::Test and whose internal state is accessible to tests that use it. This is a critical distinction that might be a bit difficult to understand for users of frameworks in other languages. Essentially, instead of being part of the test fixture class, the tests related to a fixture are external. Yeah, it’s weird but that’s what we have.

A test fixture can define set-up and tear-down actions in either SetUp()/TearDown() or in the constructor and destructor. For example, if I want to test a BankAccount, my test fixture might appear as:

struct BankAccountTest : testing::Test
{
  BankAccount* account;
  BankAccountTest()
  {
    account = new BankAccount;
  }
  virtual ~BankAccountTest()
  {
    delete account;
  }
};

But the test fixture is not an actual test: it’s just rules for setting up and destroying objects that you need for testing. (Of course, we could have used a unique_ptr but I wanted to show a destructor, so there.) Anyways, the actual test now uses a TEST_F instead of the usual TEST, and its first argument is the fixture:

TEST_F(BankAccountTest, CanDepositMoney)
{
  account->deposit(100);
  EXPECT_EQ(100,account->balance);
}

With ReSharper C++, you can run this test by pressing Alt+Enter and choosing Run:

Choosing this option causes the program to be compiled and the test executed. You can get a visual read-out on the state of your tests in the Unit Test Sessions window:

How Do I Test Lots of Data?

Data-driven tests? I’m glad you asked, because Google Test has something called parameterized tests. Essentially, you can just set up a set of values and feed them all consecutively into a test. First of all, set up your structure for running the test:

struct account_state
{
  int initial_balance;
  int withdraw_amount;
  int final_balance;
  bool success;
  friend std::ostream& operator<<(std::ostream& os, const account_state& obj)
  {
    return os
      << "initial_balance: " << obj.initial_balance
      << " withdraw_amount: " << obj.withdraw_amount
      << " final_balance: " << obj.final_balance
      << " success: " << obj.success;
  }
};

The above is a statement of before-and-after states for a bank account. We are testing the process of withdrawing money. Oh, notice how I used R++ to generate the operator<< pretty-print: this is necessary because Google Test has no idea how to print account_state objects to the console… and we kind of need this for good-looking tests.

Now comes the tricky part: we want to reuse our previous BankAccountTest fixture but, at the same time we want to use account_state instances to initialize the balance. Here’s how it’s done:

struct WithdrawAccountTest : BankAccountTest, testing::WithParamInterface<account_state>
{
  WithdrawAccountTest()
  {
    account->balance = GetParam().initial_balance;
  }
};

The magic sauce here is in the WithParamInterface<> class as well as the GetParam() function, which yields the parameter being used in the current test case. Finally, we can write the test itself…

TEST_P(WithdrawAccountTest, FinalBalance)
{
  auto as = GetParam();
  auto success = account->withdraw(as.withdraw_amount);
  EXPECT_EQ(as.final_balance,account->balance);
  EXPECT_EQ(as.success,success);
}

Notice the use of TEST_P here. The rest is pretty much the same: we get the test values with GetParam(), extract what we need, perform the test and check not one but two values. So finally, the icing on the cake is in us defining the test cases for this test. Using the magic of uniform initialization we can write it as follows:

INSTANTIATE_TEST_CASE_P(Default, WithdrawAccountTest,
  testing::Values(
  account_state{100,50,50,true},
  account_state{100,200,100,false}
  ));

When working with parameterized tests, you run them just as you would run a single test or a test based on a test fixture. The difference is in the output: each case takes up a separate line in the tree of test cases:

There’s More!

Google Test is a really big and comprehensive framework. Together with its sister, Google Mock, they provide ample possibilities for unit testing. Check out the advanced guide to find out more about sophisticated Google Test practices.

And here’s a video illustrating the story described above.

Hey, did I mention that ReSharper C++ supports Google Test? Well, it does. So check it out if you haven’t already. ■

image description