Lab 2 Test Driven Development
Aims
The aim of this lab is to introduce the process of TDD and develop some simple tests.
- Multi File Build and Project Setup
- Build Tools (cmake and make test)
- Using gtest and git
- Test Driven Design of a Simple RGBA structure
Getting Started
To start this project we are going to build a typical software project setup. By creating the following folders
directory | usage |
---|---|
src | used for C++ source files .cpp |
include | used for C++ header files .h |
tests | used for tests and related files |
mkdir RGBA
cd RGBA
mkdir src include tests
We will add other resources as needed but for now this will be enough to get us started. From here we will create a simple “hello world” project to ensure we can build our project. For this example we will create an empty file src/main.cpp and an Empty CMakeLists.txt file as follows.
touch src/main.cpp
touch CMakeLists.txt
Our CMakeLists.txt file will look like this.
# We will always try to use a version > 3.1 if avaliable
cmake_minimum_required(VERSION 3.2)
if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{CMAKE_TOOLCHAIN_FILE})
set(CMAKE_TOOLCHAIN_FILE $ENV{CMAKE_TOOLCHAIN_FILE})
endif()
# name of the project It is best to use something different from the exe name
project(RGBA_build)
# Here we set the C++ standard to use
set(CMAKE_CXX_STANDARD 17)
# Now we add our target executable and the file it is built from.
add_executable(RGBA)
# We now add the source files we want for the project. for now we are going to just add main.cpp
# See here for more details https://crascit.com/2016/01/31/enhanced-source-file-handling-with-target_sources/
target_sources(RGBA PRIVATE src/main.cpp)
and src/main.cpp like this
#include <iostream>
#include <cstdlib>
int main()
{
std::cout<<"RGBA \n";
return EXIT_SUCCESS;
}
We now have a base working project we can build upon.
Adding Tests
./vcpkg install gtest
in your vcpkg directory so cmake can find the required files, and that the CMAKE_TOOLCHAIN_FILE environment variable is also set.
see here for more detailsWe are now going to add a new section to our CMakeLists.txt file to enable testing. We are also going to add an empty test file for our project.
touch tests/RGBATests.cpp
We add the following code
#include <gtest/gtest.h>
int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc,argv);
return RUN_ALL_TESTS();
}
Then add this to the existing CMakeLists.txt file. I have placed it at the end of the file.
#################################################################################
# Testing code
#################################################################################
find_package(GTest CONFIG REQUIRED)
include(GoogleTest)
enable_testing()
add_executable(RGBATests)
target_sources(RGBATests PRIVATE tests/RGBATests.cpp)
target_link_libraries(RGBATests PRIVATE GTest::gtest GTest::gtest_main)
gtest_discover_tests(RGBATests)
As we have modified the CMakeLists.txt file it is best to remove the CMakeCache.txt file before re-building as follows.
Using git for version control
Now we have a simple template project it will help if we add this to version control, for this we are going to use git. At present we are going to just use a local repository so we can track changes. Later we will upload this to github for cloud storage.
First we need to tell git who we are and give it an email address (this helps git to know who is making the commits and who to blame!).
git config --global user.name " put your name here"
git config --global user.email " put your email here"
I usually recommend using the same username as your github account same for the email address.
Setting up a repository
When using git for version control we usually only put in source files (.cpp / .h) text files / resources needed and build files. Anything that can be generated (.o / .a / .dll .exe etc) or binary files are usually excluded as they do not work that well under version control.
To ignore certain files / directories we can generate a local file called .gitignore or we can add certain files to a global git ignore.
For this project we are going to create a local one as follows in the root project folder.
touch .gitignore
we will then add the wildcard build/* which will exclude the build directory.
build/*
creating a new repository
Before we can use our project for version control we need to initialize it as follows
git init
We can then add the current files in the folder by using
git add .
note that git will ignore the files in the .gitignore file, finally we commit the changes to the repository using
git commit -am "initial commit"
The flag -a commits all files and -m is the commit message. If this is omitted git will open up the default system editor (vim) and ask to to fill in a message. This link has some good ideas on best practices for commit messages.
This is our usual process, if we add more files we do a git add (can name individual files or use . for a recursive add). Make changes, then commit. There are no hard and fast rules for when to commit but I tend to do once I have completed a “unit” of code / a passing test.
We will return to git later but for now we will continue with some code development / unit testing.
RGBA.h
We are going to add a file called rgba.h to the folder include
touch include/rgba.h
We will add the following include guard to the file
#ifndef RGBA_H_
#define RGBA_H_
#endif
And if we add #include "rgba.h"
to our other .cpp files and type make we get the following
we need to tell cmake where to search for include files. This can be set for the whole project if we add it before the individual executable definitions as follows
# add include paths
include_directories(include)
We can now start doing our TDD process, by reminding ourselves of the 3 rules of TDD.
- You are not allowed to write any production code unless it is to make a failing unit test pass.
- You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
- You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
So for part one we are going to write a test using gtest, in the file RGBATest.cpp we will add the following.
TEST(RGBA,construct)
{
RGBA a;
}
As you now see we have a compilation failure so we now need to fix this, we can do this by adding the following to RGBA.h
struct RGBA
{
};
As you can now see we have a compiling bit of code but no real tests as yet. Lets repeat the cycle, and add to the original test
ASSERT_TRUE(a.r == 0);
ASSERT_TRUE(a.g == 0);
ASSERT_TRUE(a.b == 0);
ASSERT_TRUE(a.a == 0);
and again we have a failing test so let’s fix.
So we will write enough code to pass this test by modifying the RGBA struct as follows
struct RGBA
{
union
{
uint32_t pixels=0;
struct
{
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char a;
};
};
};
Lets commit
Now we have a passing test and working code it’s time to do a commit.
First let’s see the status of our project by issuing the command
git status
You can see that it reports several files have changed and the the include directory contains files not added to version control. We can add the include directory as follows
git add include
Then do a commit.
Exercise
Using the same processes as above add the following and test.
- =default constructor and copy constructor
- A user defined constructor taking in r,g,b,a values
RGBA(unsigned char _r, unsigned char _g, unsigned char _b unsigned char _a)
- A set method taking in r,g,b,a values
void set(unsigned char _r, unsigned char _g, unsigned char _b unsigned char _a)
- A clamp method of the format
void clamp(unsigned char _min, unsigned char _max);
Which will clamp the individual r,g,b,a values between the ranges _min and _max
References
Enhanced source file handling with target_sources()
Building GoogleTest and GoogleMock directly in a CMake project
Git Internals - Plumbing and Porcelain