Test Cases using GTest (Unit Testing)

Hi all,
As the tradition goes on this blog, you can also find this post in video format with practical example on my YouTube channel (/c/cppdev), right here(second part of the video) and here.

Introduction

This post will cover a minimal example on how you can create UTs (Unit Tests) for your code and how you should go about it.

Don’t forget that you can also find a practical example on the YouTube channel which will present test cases for the Tic Tac Toe game which was implemented in this post.

After you follow this post, you can always go there to maybe find more information. The full source code for that example can be found here.

Unit Testing

First, a little bit about unit testing.

Unit testing means writing code that verifies individual parts, or units, of an application or library. A unit is the smallest testable part of an application. Unit tests assess code in isolation.

In C++ this means writing tests for a single class, for example. Tests only examine code within a single object. They don’t rely on external resources such as databases, web servers, or message brokers.

Thinking process

Let me first note that the better (or correct, if I may say so) approach to development is actually the TDD (Test Driven Development) which simply means that you should first create the test cases for your code, then implement the actual code. However, for this example, let’s just say you already have the source code and you just want to test it.

The most important thing, when you want to create UTs for your code, is to have a good understanding of the functionality of your units should be. In this case, the units will be the actual classes of our project.

Another thing to keep in mind is that, we only want to test the unit and nothing outside of it.

So, as an example, let’s say we have a simple class Something which has only three functions : void print(std::string msg), int getResult(int a, int b) and bool isCondition(bool a, bool b).

In this simple case, we know what each function has to do and what it has to return. We don’t need to know the specifics of the function, but we need to know the output that should happen for a given input.

Having this information, we will have at least one test case for each function declared above.

Actual test cases implementation

Because we already have the GTest library linked from previous post, we are getting right into implementing the test case.

Let’s first define the prerequisites. First the class Something example (Something.hpp) :

class Something
{
public:
    // should print the given input
    void print(std::string str)
    {
         std::cout << str;
    }

    // should return sum of the given input
    int getResult(int a, int b)
    {
        return a + b;
    }

    // should check the condition based on input
    bool isCondition(bool a, bool b)
    {
        return a && b;
    }
};

We have the above class and we also already know what the functions should do. We need to have at least 1 test case for each of the functions in the "Something" class to verify if the output is the good one.

I know that these are very simple examples and it is obvious that it should work correctly, but even so, these should be done. The reason behind this is that, maybe in the future, someone from your team is going to change the functionality of the functions to some other, not intended by you. You already guessed what happens in this case… the test cases will fail which will signal to the team.

Now, for the test cases, we first have to define a fixture. We will name it SomethingTests. The fixture will have the "Something" class as a data member and will have functions "SetUp" and "TearDown" overloaded. These functions are provided by the gtest library and they will be called every time a test case is executed in this form : "SetUp" -> Test Case Body -> "TearDown".

As I’ve already mentioned, the tested class will be a data member of the fixture. Good practice for this is to call it "sut", which is an abbreviation for "System Under Test". The unit that are testing will be the actual system under test.

// include for gtest library
#include <gtest/gtest.h>

// include for the sut
#include "Something.hpp"

// we need to inherit from "Test" in namespace "testing"
struct SomethingTests : public ::testing::Test
{
    // to be executed at beginning of test execution
    void SetUp() override
    {
         // we don't need anything at the moment
         // but we could, for eg., just have the sut as pointer
         // and initialize it every time here
    }

    // to be executed at the end of test execution
    void TearDown() override
    {
         // we don't need anything at the moment
         // but we could, for eg., just have the sut as pointer
         // and free it every time here
    }
 
    // the System Under Test
    Something sut_;
};

Now that we have the fixture created, we can go ahead and implement the actual test cases. The test cases will have the form of : TEST_F(<FixtureName>, <TestCaseName>) { <TestBody> }.

Remember, we are going to create at least three test cases. One for each of the functions available in class "Something".

The actual verification will be done using all the helper functions/macros provided by the gtest library. The full documentation to these can be found in the googletest git repository.

We will mostly use the helpers that have the form of : "EXPECT_<MATCHER>". For e.g. "EXPECT_EQ" – expect equals, "EXPECT_GT" – expect greather than, "EXPECT_TRUE" – self explanatory.

TestCase for “print” function:

TEST_F(SomethingTests, ShouldPrint)
{
    // prepare the input
    std::string in("some text");
    // prepare the expected output
    std::string out = in;

    // start capturing the std out
    testing::internal::CaptureStdout();

    // call our tested function
    sut_.print(in);

    // check if the output is expected
    EXPECT_EQ(testing::internal::GetCapturedStdout(), out);
}

TestCase for “getResult” function:

TEST_F(SomethingTests, ShouldGetResult)
{
    // prepare the input
    int aIn = 3;
    int bIn = 5;

    // prepare the expected output
    int out = 8;

    // check if the output is expected
    EXPECT_EQ(sut_.getResult(aIn, bIn), out);

TestCase for “isCondition” function:

TEST_F(SomethingTests, ShouldCheckIsConditionTrue)
{
    // prepare the input
    bool aIn = true;
    bool bIn = false;

    // check if the output is expected
    EXPECT_FALSE(sut_.isCondition(aIn, bIn));
}

Conclusion

So, that is more or less all when you want to create simple test cases for your project.

I hope that, every one of you learned something new here and you will use this information in the future in order to test your project.

Don’t forget that you can find programming related videos on my YouTube channel (/c/cppdev) so go check it out!

Thanks for reading,
cppdev, out.

1 thought on “Test Cases using GTest (Unit Testing)”

Leave a Reply

%d bloggers like this: