Published on

An Introduction to Google Test

Authors

I started using googletest.

Over the last few months, I built an SDK for controlling a product that uses industrial cameras from an external application. Because it was a new product, I was trusted with everything from the design onward. I took that as a chance to introduce some new practices, such as deciding on a coding style and adding tests. I had never seriously used a testing framework before. My impression after using it was that, yes, writing tests is a hassle, but they were extremely useful when deciding whether a change was safe to commit or release. I would definitely like to use them again next time.

The SDK was probably under 10,000 lines of code, and I only wrote tests for the public API exposed to users. I may not have used the framework to its full potential, but I would like to introduce the parts I actually used. I will not try to explain how to design tests well, because I am not really in a position to do that.

Installation

Download the latest version from googletest and build it. Change the runtime-library linkage format as needed for your environment. Once the build is complete, you get the following two libraries.

  • gtest.lib
  • gtest_main.lib

When using them, add the include path and link both gtest and gtest_main. If you link gtest_main, you do not need to write your own main function. If you do not link it, you need to provide main yourself.

If you write main, it looks like this.

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

Basic usage

Define tests with the TEST() macro. The basic form is as follows.

TEST(test_case_name, test_name) {
.... test body ....
}

For example:

#include <gtest/gtest.h>

int Increment(int val) { return val + 1; }
int Decrement(int val) { return val - 1; }

TEST(HelloGoogleTest, Increment)
{
  EXPECT_EQ(2, Increment(1));
}

TEST(HelloGoogleTest, Decrement)
{
  EXPECT_EQ(0, Decrement(1));
}

Inside the TEST macro, you check return values and other conditions with EXPECT_XX or ASSERT_XX macros. There are many of these depending on the type of check, but in the SDK I wrote, I only used the following ones. See the reference link for details.

MacroMeaning
EXPECT_EQChecks whether two values are equal
EXPECT_TRUEChecks whether the value is true
EXPECT_DOUBLE_EQChecks whether two double-precision floating-point values are equal
EXPECT_STREQChecks whether two C strings are equal

If a test succeeds, the output shows OK. If it fails, it shows Failed along with the actual values.

google_test_passedgoogle_test_failed

In many cases, that is enough. However, when you test classes, you may want to share setup and teardown logic across tests. In that case, you need to structure the tests differently depending on the scope of the shared data, as described below.

Sharing resources across multiple tests

If you want to initialize the same data for each test, use a test fixture. In the sample below I only show initialization, but setup and cleanup are handled with SetUp and TearDown.

#include <iostream>
#include <vector>

#include <gtest/gtest.h>

namespace {

class HelloGoogleTest: public ::testing::Test {
protected:
  HelloGoogleTest()
  {
    std::cout << "HelloGoogleTest()" << std::endl;
  }

  ~HelloGoogleTest()
  {
    std::cout << "~HelloGoogleTest()" << std::endl;
  }

  virtual void SetUp()
  {
    std::cout << "SetUp()" << std::endl;
    vec_.push_back(0);
    vec_.push_back(1);
    vec_.push_back(2);
  }

  virtual void TearDown()
  {
    std::cout << "TearDown()" << std::endl;
  }

  static void SetUpTestCase()
  {
    std::cout << "SetUpTestCase()" << std::endl;
  }

  static void TearDwonTestCase()
  {
    std::cout << "TearDownTestCase()" << std::endl;
  }

  std::vector<int> vec_;
};

TEST_F(HelloGoogleTest, Size) {
  EXPECT_EQ(3, vec_.size());
}

TEST_F(HelloGoogleTest, Value) {
  EXPECT_EQ(0, vec_[0]);
  EXPECT_EQ(1, vec_[1]);
  EXPECT_EQ(2, vec_[2]);
}

TEST_F(HelloGoogleTest, Clear) {
  EXPECT_EQ(3, vec_.size());
  vec_.clear();
  EXPECT_EQ(true, vec_.empty());
}

} // namespace

The screenshot below shows the output of that test run. SetUp and TearDown are executed for each test.

google_test_passed

Sharing resources across tests in the same test case

In the previous section, initialization ran every time a test started. Sometimes initialization is expensive, and you want to run it only once before the whole group of tests. In that case, define static variables and perform initialization and cleanup in SetUpTestCase and TearDwonTestCase.

In the screenshot above, TearDwonTestCase does not seem to have run for some reason, but you can see that SetUpTestCase is executed only once at the start of the test case. This is the behavior you can use.

#include <gtest/gtest.h>

namespace {

class HelloGoogleTest: public ::testing::Test {
protected:
  HelloGoogleTest()
  {
  }

  ~HelloGoogleTest()
  {
  }

  virtual void SetUp()
  {
  }

  virtual void TearDown()
  {
  }

  static void SetUpTestCase()
  {
    Initialize(system_);
    device_ = OpenDevice(system_);
  }

  static void TearDwonTestCase()
  {
    CloseDevice(device_);
    Finalize(system_);
  }

  static System* system_;
  static Device* device_;
};

System* HelloGoogleTest::system_ = NULL;
Device* HelloGoogleTest::device_ = NULL;

} // namespace

Initialization that runs when testing starts

Define a class derived from ::testing::Environment, override SetUp and TearDown, and pass an instance of that class to ::testing::AddGlobalTestEnvironment from main.

namespace {

class TestEnvironment : public ::testing::Environment {

  virtual void SetUp()
  {
    // Initialization at program start
  }

  virtual void TearDown()
  {
    // Cleanup at program exit
  }

};

} // namespace

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

Writing a test report

Pass an argument when running the test executable. By default, the result is written to test_detail.xml.

test.exe --gtest_output=xml

If you want to change the file name, add it to the option like this.

test.exe --gtest_output=xml:test_report.xml

That produces a file like the following.

<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="3" failures="0" disabled="0" errors="0" timestamp="2014-02-26T16:46:06" time="0.017" name="AllTests">
  <testsuite name="HelloGoogleTest" tests="3" failures="0" disabled="0" errors="0" time="0.013">
    <testcase name="Size" status="run" time="0.004" classname="HelloGoogleTest" />
    <testcase name="Value" status="run" time="0.002" classname="HelloGoogleTest" />
    <testcase name="Clear" status="run" time="0.004" classname="HelloGoogleTest" />
  </testsuite>
</testsuites>

Adding extra output to the report

If you want to add custom items to the test report, use the RecordProperty function.

#include <iostream>
#include <vector>

#include <gtest/gtest.h>

namespace {

class HelloGoogleTest: public ::testing::Test {
protected:
  HelloGoogleTest()
  {
    std::cout << "HelloGoogleTest()" << std::endl;
  }

  ~HelloGoogleTest()
  {
    std::cout << "~HelloGoogleTest()" << std::endl;
  }

  virtual void SetUp()
  {
    std::cout << "SetUp()" << std::endl;
    vec_.push_back(0);
    vec_.push_back(1);
    vec_.push_back(2);
  }

  virtual void TearDown()
  {
    std::cout << "TearDown()" << std::endl;
  }

  static void SetUpTestCase()
  {
    std::cout << "SetUpTestCase()" << std::endl;
  }

  static void TearDwonTestCase()
  {
    std::cout << "TearDownTestCase()" << std::endl;
  }

  std::vector<int> vec_;
};

TEST_F(HelloGoogleTest, Size) {
  RecordProperty("ID", 0);
}

TEST_F(HelloGoogleTest, Value) {
  RecordProperty("ID", 1);
}

TEST_F(HelloGoogleTest, Clear) {
  RecordProperty("ID", 2);
}

}

Running the test above produces a file like the following. RecordProperty only supports output of type std::string and int, so if you want to write something like a double, convert it to a string first with ::testing::PrintToString.

<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="3" failures="0" disabled="0" errors="0" timestamp="2014-02-26T16:51:08" time="0.013" name="AllTests">
  <testsuite name="HelloGoogleTest" tests="3" failures="0" disabled="0" errors="0" time="0.01">
    <testcase name="Size" status="run" time="0.002" classname="HelloGoogleTest" ID="0" />
    <testcase name="Value" status="run" time="0.002" classname="HelloGoogleTest" ID="1" />
    <testcase name="Clear" status="run" time="0.004" classname="HelloGoogleTest" ID="2" />
  </testsuite>
</testsuites>

Reference

Japanese translation of the GoogleTest documentation