gtest testing framework foundation of c++ unit testing

unit testing

Module level test, different from system test and integration test, is a fine-grained test for the subdivided unit of software module, which should be carried out in the development process.

Unit Test (module test) is a small piece of code written by developers, which is used to check whether a very small and clear function of the tested code is correct. By writing Unit Test, program coding errors, even program design errors, can be found in the coding stage.

Unit testing has many benefits:

  • Increase developer confidence
  • Fast regression test after code modification
  • Improve development quality
  • Accelerate iteration

Unit testing doesn't have to start from scratch. There are many excellent testing frameworks to choose from. This paper mainly introduces Google's gtest framework. Its GitHub address is https://github.com/google/google test

gtest installation

Download the release version from github release. The latest version is v1.10.0. Take the installation under windows as an example. It is similar under Linux.

After decompression, enter the directory, create a new build directory, and open the terminal in the build directory to execute: cmake.

After completion, the gtest.sln file will be generated in the current directory, opened with vs2015, selected platform, compiled GTEST, and compiled static library by default.

About the difference between gtest and gtest Main:

  • gtest is the gtest Library
  • gtest main only has more main functions than gtest. Use gtest Library
  • You don't have to write your own main function when using GTEST main. I'll talk about it later

Use

gtest provides a lot of macros for testing, which is very convenient to use. Let's not list them for the moment, let's look at the examples first.

Open vs2015 to create a new empty project. This example demonstrates unit testing of functions and classes.

Configure the header file and library file of gtest, omitted.

Create a new file for testing functions, such as demo1.cpp:

// demo1.cpp for testing a single function
#include <iostream>
#include <gtest/gtest.h>

using namespace std;

// Function to be tested
int Foo(int a, int b)
{
	if (a == 0 || b == 0)
	{
		throw "don't do that";
	}
	int c = a % b;
	if (c == 0)
		return b;
	return Foo(b, c);
}

// TEST code, using the macro TEST provided by gtest
TEST(FooTest, HandleZeroInputAndException)
{
	EXPECT_EQ(2, Foo(4, 10)); // Program run results can be checked by ASSERT * and extend * assertions
	EXPECT_EQ(6, Foo(30, 18));
	EXPECT_ANY_THROW(Foo(10, 0)) << "params: " << 10 << ", " << 0;
	EXPECT_THROW(Foo(0, 5), char*);
}

This completes the flow of test function, adding main function can test. For specific functions, multiple test cases need to be written.

The code of the test class (including 2 CPPs and 1 h) is as follows:

// Configure.h

#pragma once

#include <string> 
#include <vector> 

class Configure
{
private:
	std::vector<std::string> vItems;

public:
	int addItem(std::string str);
	std::string getItem(int index);
	int getSize();
};

// Configure.cpp
#include "Configure.h" 
#include <algorithm> 

/**
* @brief Add an item to configuration store. Duplicate item will be ignored
* @param str item to be stored
* @return the index of added configuration item
*/
int Configure::addItem(std::string str)
{
	std::vector<std::string>::const_iterator vi = std::find(vItems.begin(), vItems.end(), str);
	if (vi != vItems.end())
		return vi - vItems.begin();

	vItems.push_back(str);
	return vItems.size() - 1;
}

/**
* @brief Return the configure item at specified index.
* If the index is out of range, "" will be returned
* @param index the index of item
* @return the item at specified index
*/
std::string Configure::getItem(int index)
{
	if (index >= vItems.size())
		return "";
	else
		return vItems.at(index);
}

/// Retrieve the information about how many configuration items we have had 
int Configure::getSize()
{
	return vItems.size();
}

// ConfigureTest.cpp
#include <gtest/gtest.h> 
#include "Configure.h" 

// Test function
TEST(ConfigureTest, addItem)
{
	// do some initialization 
	Configure* pc = new Configure();

	// validate the pointer is not null 
	ASSERT_TRUE(pc != NULL);

	// call the method we want to test 
	pc->addItem("A");
	pc->addItem("B");
	pc->addItem("A");

	// validate the result after operation 
	EXPECT_EQ(pc->getSize(), 2);
	EXPECT_STREQ(pc->getItem(0).c_str(), "A");
	EXPECT_STREQ(pc->getItem(1).c_str(), "B");
	EXPECT_STREQ(pc->getItem(10).c_str(), "");

	delete pc;
}

// Program entry
int main(int argc, char* argv[])
{
	testing::InitGoogleTest(&argc, argv);
	RUN_ALL_TESTS();
	std::cin.get();

	return 0;
}

The last main of the code is the built-in main in GTEST main library

Run all tests() runs all TEST cases in the project, including tests defined for functions and tests defined for classes (from different files). The function returns 0 for success and 1 for failure.

From the example, we can see the general usage of gtest:

  • Create a test file for each class to be tested, and implement the unit test code in this file
  • Create a file containing the main function from which to run all unit tests
  • Compile link to run

When writing test code:

  • The first parameter of the TEST function is the name of the TEST suite, and the second parameter is the name of the TEST case in the TEST suite. It is user-defined
  • gtest groups the test results according to the test suite name, so if it is an associated test, you should set their first parameter to be the same.
  • Different test suites can have the same test case name

There are two test suites in the above test code, each with one test case.

The results of the above tests are as follows:

The convenience of gtest

The first choice is to assert the test macro.

  1. Basic assertion

Be careful:

  • A non fatal failure occurs when the assertion of the extend * version fails, and the current function will not be aborted
  • ASSERT * version of ASSERT fails with a fatal failure and ends the current function
  • Extend * is used for assertions that processing results of subsequent test logic will not be affected even if the failure occurs, such as checking multiple attributes of a result returned by a method
  • ASSERT * is often used to ASSERT the processing results of mandatory dependency of subsequent test logic, such as checking whether the pointer is empty after creating an object, and if it is empty, subsequent object method calls will fail
  1. Binary comparison

  1. string comparison

The second is to filter the test cases to be executed through command line parameters.

When executing a program, you can specify command line parameters:

  • . / foo'u test no filter specified, run all tests
  • . / foo'u test -- gtest'u filter = * specify filter as *, run all tests
  • . / foo'u test -- gtest'u filter = FooTest. * run all tests of test case FooTest
  • . / foo_test --gtest_filter=Null:Constructor runs all tests with full name (i.e. test case name + ". + test name, such as GlobalConfigurationTest.noConfigureFileTest) containing" Null "or" Constructor "
  • . / foo'u test -- gtest'u filter = FooTest. * - FooTest.Bar run all tests of test case FooTest, but not FooTest.Bar

This feature can be useful in projects with a large number of test cases.

For higher-level usage, refer to the documentation.

Reference material

googletest
Googletest Primer
googletest make options
Easy to write C + + unit tests

123 original articles published, 167 praised, 350000 visitors+
Private letter follow

Keywords: Google github Windows Linux

Added by smarthouseguy on Mon, 20 Jan 2020 13:47:16 +0200