Continuous Code Coverage with gcc, googletest, and Hudson
Heres a little recipe for getting code coverage metrics for your C++ unit tests using gcc, googletest and hudson.
Suppose we have a little class that we’d like to get under continuous integration:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#ifndef PROGRESSSTATE_H_
#define PROGRESSSTATE_H_
class
ProgressState {
public
:
ProgressState(unsigned
int
target);
virtual
~ProgressState();
unsigned
int
getPercentage();
void
setValue(
int
value);
private
:
unsigned
int
m_value;
unsigned
int
m_target;
};
#endif /* PROGRESSSTATE_H_ */
|
And here is the implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include "ProgressState.h"
ProgressState::ProgressState(unsigned
int
target)
: m_value(0), m_target(target)
{}
ProgressState::~ProgressState(){}
void
ProgressState::setValue(
int
value)
{
if
(value < 0){
m_value = 0;
}
else
if
(value > (
int
)m_target){
m_value = m_target;
}
else
{
m_value = value;
}
}
unsigned
int
ProgressState::getPercentage()
{
return
m_value * 100 / m_target;
}
|
Continuous Build
First we must install Hudson on the build machine. Hudson is available for most operating systems and it is pretty easy to install. Once hudson is up and running we will need to install these plugins from our hudson management console:
- Git Plugin: To poll the git repository for your code
-
Cobertura Plugin: To gather the test coverage results a publish them with the build
The next step is to create a hudson job for building our project.
Then we need a way for Hudson to get access to the source code. The typical way to do this is to get hudson to poll from your version control system. Using git this is pretty easy.
One thing to remember is to set up the git user account for the hudson server. To do this in Ubuntu you need to issue the following commands:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
mike@mike-laptop:~$
sudo
-s -u hudson
hudson@mike-laptop:~$
cd
/var/lib/hudson/jobs/coverage/workspace
hudson@mike-laptop:
/var/lib/hudson/jobs/coverage/workspace
$
ls
-al
total 20
drwxr-xr-x 5 hudson nogroup 4096 2010-05-31 22:33 .
drwxr-xr-x 4 hudson nogroup 4096 2010-05-31 22:33 ..
drwxr-xr-x 3 hudson nogroup 4096 2010-05-31 22:33 Debug
drwxr-xr-x 8 hudson nogroup 4096 2010-05-31 22:33 .git
-rw-r--r-- 1 hudson nogroup 0 2010-05-31 22:33 README
drwxr-xr-x 2 hudson nogroup 4096 2010-05-31 22:33 src
hudson@mike-laptop:
/var/lib/hudson/jobs/coverage/workspace
$
hudson@mike-laptop:
/var/lib/hudson/jobs/coverage/workspace
$ git config user.email
"some@email.com"
hudson@mike-laptop:
/var/lib/hudson/jobs/coverage/workspace
$ git config user.name
"hudson"
hudson@mike-laptop:
/var/lib/hudson/jobs/coverage/workspace
$
|
Generating the coverage statistics with gcov
To get gcc to instrument the generated binary with coverage and profiling (necessary for branch stats) code we must provide the compile with these two additional options: -fprofile-arcs -ftest-coverage. And we must link the final executable with -lgcov. Now when the coverage test executable is run it will output .gcda and .gcno files with the coverage statistics. These settings are set in the makefiles for the project.
Write some tests
Breaking from TDD conventional wisdom for the purposes of this article, lets write some tests for the production code we already have.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#include <gtest/gtest.h>
#include "ProgressState.h"
TEST(ProgressStateTest, zeroValueOfValidTargetIsZeroPercent)
{
ProgressState progress(100);
progress.setValue(0);
ASSERT_EQ((unsigned
int
) 0, progress.getPercentage());
}
TEST(ProgressStateTest, negativeValueOfValidTargetIsZeroPercent)
{
ProgressState progress(100);
progress.setValue(-100);
ASSERT_EQ((unsigned
int
) 0, progress.getPercentage());
}
TEST(ProgressStateTest, valueEqualTargetIsHundredPercent)
{
ProgressState progress(200);
progress.setValue(200);
ASSERT_EQ((unsigned
int
) 100, progress.getPercentage());
}
|
This looks like a pretty good start, right? Now lets run the tests and collect the statistics in Hudson. To do this we need to add a build step to our Hudson job.
The first three lines of the command simply execute the build. The command on line 4 executes the binary test application we have built, and outputs the test result summary to a junit format XML file.
The final two commands are where the magic is. This executes the gcovr script, a handy python script that converts the gcov output to a Cobertura-style XML file.
Then we have to tell hudson to search the build workspace for the junit and coverage xml files as a post-build action. Now when we run the build we get nice overview charts trending out unit test results and code coverage.
Then when we drill down into the specific source file we can see quite clearly that we have missed a test scenario.
Links
Hudson Continuous Integration Server:
All the source code and makefiles are available publicly on my github account:
http://github.com/meekrosoft/coverage
William E. Hart’s Blog post on gcovr:
http://wehart.blogspot.com/2009/07/summarizing-gcov-coverage-statistics.html