C modules unit-testing in Linux
In spite of its age, C programming language is still very popular, especially for developing system or low-level software like drivers, compilers, virtual machines etc. And as any software, it have to be tested. Let me show brief introduction in unit-testing for C modules.
There are many unit-testing frameworks for C, and one of the most well-known is cmockery. But I'll show the usage of much more simpler "framework" - FCTX. The main advantage of it is that it consists of just one header file, so it can be easily used for test tasks, small projects and examples.
For calculating code coverage I use gcov/lcov tools. Gcov is included in GCC, so you don't have to install it. Lcov is a graphical front-end for Gcov and should be installed from the repository:
As a sample code for testing I'll use a simple hash function from Robert Sedgwicks Algorithms in C book:
The tests:
Tests are compiled and started as a usual command-line utility:
$ gcc hash.c test.c -o hash
$ ./hash
test_hash ......................................................... PASS
test_hash_with_empty_string ....................................... PASS
----------------------------------------------------------------------------
PASSED (2/2 tests)
Now let's check code coverage. For it the program should be compiled with the coverage information included. Lcov analyzes the information and generates a HTML-ready result.
$ gcc -fprofile-arcs -ftest-coverage -coverage hash.c test.c -o hash
$ lcov --directory . --zerocounters
Deleting all .da files in . and subdirectories
Done.
$ ./hash
test_hash ......................................................... PASS
test_hash_with_empty_string ....................................... PASS
----------------------------------------------------------------------------
PASSED (2/2 tests)
$ lcov --directory . --capture --output-file hash.info
Capturing coverage data from .
Found gcov version: 4.4.4
Scanning . for .gcda files ...
Found 2 data files in .
Processing test.gcda
Processing hash.gcda
Finished .info-file creation
Overall coverage rate:
lines......: 57.1% (560 of 981 lines)
functions..: 67.5% (83 of 123 functions)
branches...: 37.0% (247 of 668 branches)
$ genhtml hash.info
Reading data file hash.info
Found 3 entries.
Found common filename prefix "/home/nuald"
Writing .css and .png files.
Generating output.
Processing file workspace/code-coverage/test.c
Processing file workspace/code-coverage/fct.h
Processing file workspace/code-coverage/hash.c
Writing directory view page.
Overall coverage rate:
lines......: 57.1% (560 of 981 lines)
functions..: 67.5% (83 of 123 functions)
branches...: 37.0% (247 of 668 branches)
After it, the "index.html" file is waiting for review in the current directory.
Let's examine is the hash.c module 100% covered:
No, some code lines aren't covered by tests. Lcov shows these lines:
Let's update the tests to include testing of the required code:
Let's make the new coverage report:
$ genhtml hash.info
Now we can see that the module is fully covered:
That's all. Unit-testing is very useful technique, and can be applied in almost all the software projects. Use it as much as possible, and you'll understand all the advantages it can give you. Happy testing!
There are many unit-testing frameworks for C, and one of the most well-known is cmockery. But I'll show the usage of much more simpler "framework" - FCTX. The main advantage of it is that it consists of just one header file, so it can be easily used for test tasks, small projects and examples.
For calculating code coverage I use gcov/lcov tools. Gcov is included in GCC, so you don't have to install it. Lcov is a graphical front-end for Gcov and should be installed from the repository:
$ sudo apt-get install lcov
As a sample code for testing I'll use a simple hash function from Robert Sedgwicks Algorithms in C book:
#include "hash.h"
unsigned int RSHash(char* str, unsigned int len) {
unsigned int b = 378551;
unsigned int a = 63689;
unsigned int hash = 0;
unsigned int i = 0;
for (i = 0; i < len; ++str, ++i) {
char ch = *str;
if (!ch) {
break;
}
hash = hash * a + ch;
a = a * b;
}
return hash;
}
The tests:
#include "fct.h"
#include "hash.h"
FCT_BGN() {
FCT_QTEST_BGN(test_hash) {
fct_chk_eq_int(RSHash("test", 4), 280461880);
} FCT_QTEST_END();
FCT_QTEST_BGN(test_hash_with_empty_string) {
fct_chk_eq_int(RSHash("", 0), 0);
} FCT_QTEST_END();
} FCT_END();
Tests are compiled and started as a usual command-line utility:
$ gcc hash.c test.c -o hash
$ ./hash
test_hash ......................................................... PASS
test_hash_with_empty_string ....................................... PASS
----------------------------------------------------------------------------
PASSED (2/2 tests)
Now let's check code coverage. For it the program should be compiled with the coverage information included. Lcov analyzes the information and generates a HTML-ready result.
$ gcc -fprofile-arcs -ftest-coverage -coverage hash.c test.c -o hash
$ lcov --directory . --zerocounters
Deleting all .da files in . and subdirectories
Done.
$ ./hash
test_hash ......................................................... PASS
test_hash_with_empty_string ....................................... PASS
----------------------------------------------------------------------------
PASSED (2/2 tests)
$ lcov --directory . --capture --output-file hash.info
Capturing coverage data from .
Found gcov version: 4.4.4
Scanning . for .gcda files ...
Found 2 data files in .
Processing test.gcda
Processing hash.gcda
Finished .info-file creation
Overall coverage rate:
lines......: 57.1% (560 of 981 lines)
functions..: 67.5% (83 of 123 functions)
branches...: 37.0% (247 of 668 branches)
$ genhtml hash.info
Reading data file hash.info
Found 3 entries.
Found common filename prefix "/home/nuald"
Writing .css and .png files.
Generating output.
Processing file workspace/code-coverage/test.c
Processing file workspace/code-coverage/fct.h
Processing file workspace/code-coverage/hash.c
Writing directory view page.
Overall coverage rate:
lines......: 57.1% (560 of 981 lines)
functions..: 67.5% (83 of 123 functions)
branches...: 37.0% (247 of 668 branches)
After it, the "index.html" file is waiting for review in the current directory.
Let's examine is the hash.c module 100% covered:
No, some code lines aren't covered by tests. Lcov shows these lines:
Let's update the tests to include testing of the required code:
#include "fct.h"
#include "hash.h"
FCT_BGN() {
FCT_QTEST_BGN(test_hash) {
fct_chk_eq_int(RSHash("test", 4), 280461880);
} FCT_QTEST_END();
FCT_QTEST_BGN(test_hash_with_empty_string) {
fct_chk_eq_int(RSHash("", 0), 0);
} FCT_QTEST_END();
FCT_QTEST_BGN(test_hash_with_invalid_len) {
fct_chk_eq_int(RSHash("invalid", 20), 1640248891);
} FCT_QTEST_END();
} FCT_END();
Let's make the new coverage report:
$ gcc -fprofile-arcs -ftest-coverage -coverage hash.c test.c -o hash
$ lcov --directory . --zerocounters
$ lcov --directory . --zerocounters
$ ./hash -l minimal
$ lcov --directory . --capture --output-file hash.info$ genhtml hash.info
Now we can see that the module is fully covered:
That's all. Unit-testing is very useful technique, and can be applied in almost all the software projects. Use it as much as possible, and you'll understand all the advantages it can give you. Happy testing!
Woho, great guide, exactly what I was looking for (simple and configurable). With help of few simple bash scrits and one template I can now test almost anything. Thanks.
ReplyDelete