Unit Testing Tutorial
Contents
Introduction
If you're reading this now then I am assuming you have the Embedded ARM Development Package downloaded and unzipped. There is a UNIT_TESTING_README.txt file located in the parent directory with instructions on how to set up your environment to start unit testing the SJSU project with CGreen unit testing framework.
What is Unit Testing exactly?
Unit testing is a level of software testing where individual components or modules are tested. The purpose of unit testing is to validate that your code performs as you designed it. With the use of unit testing frameworks, stubs, and mock objects, code can be manipulated and tested without ever having to load compiled code onto a MCU.
Benefits of Unit Testing
- Increase confidence in code
- Results in modular code. Modular code is more reusable.
- Saves potential time that could be wasted debugging on a live system.
- Finds bugs before they get loaded onto a live system.
- Increases reliability of code.
- Instant visual feedback of passed/failed tests
- Regression testing - changing code either results in confidence that nothing broke or confidence that something broke and you should fix it.
Getting Started
Let's first setup our main function in test_main.cpp, which will be our foundation for future unit tests in the project :
// test_main.cpp
#include <cgreen/cgreen.h>
#include <cgreen/mocks.h>
using namespace cgreen;
int main(int argc, char **argv)
{
TestSuite *suite = create_test_suite();
add_suite(suite, c_list_suite());
return run_test_suite(suite, create_text_reporter());
}
// create_test_suite(); only needs to be done once.
// Add your own "suite" of tests by calling add_suite() and providing a pointer to your suite.
// In this case, c_list_suite() is created
Now, in the test_c_list.c file, let's create the suite that we added to main() above.
// test_c_list.c
#include <cgreen/cgreen.h>
#include <cgreen/mocks.h>
// The file under test
#include "c_list.h"
TestSuite *c_list_suite()
{
TestSuite *suite = create_test_suite();
add_test(suite, add_element_to_end); // Sub-test, which we will add after.
return suite;
}
A Simple Test
Continuing on from our test_c_list.c file, let's perform one very basic test to verify the functionality of our C-code. We'll start by creating an Ensure() function. This is nothing more than a sub-routine test apart of the entire test suite we created. Typically, you will have an Ensure test function to test one specific functionality of your code. In this example, we only want to test the ability to add one element to the end of our linked list of c_list. The input argument to Ensure is simply a description of the test you're performing. Let's start this example by creating a pointer list.
Ensure(add_element_to_end)
{
c_list_ptr list = c_list_create();
assert_that(c_list_node_count(list), is_equal_to(1));
}
Compile the program by calling make, which defaults to make all. To execute the program, simply perform ./test_all.exe
$ ./test_all.exe
Running "main" (2 tests)...
Completed "foo_suite": 1 pass in 11ms.
code/test_c_list.c:15: Failure: c_list_suite -> add_element_to_end
Expected [c_list_node_count(list)] to [equal] [1]
actual value: [0]
expected value: [1]
Completed "c_list_suite": 1 pass, 1 failure in 9ms.
Completed "main": 1 pass, 1 failure in 20ms.
Oops. Looks like we got a unit test failure because we inserted a test assert that failed. The following line assert_that(c_list_node_count(list), is_equal_to(1)); tests that the c_list list has a node list count of 1, but this failed because we didn't add any items into the c_list, thus, this did not match our expectation of having a node list of 1. Let's go ahead and add that now and see if the test passes.
Ensure(add_element_to_end)
{
c_list_ptr list = c_list_create();
c_list_insert_elm_end(list, (void*) 1);
assert_that(c_list_node_count(list), is_equal_to(1));
}
Compiling and running gives:
$ ./test_all.exe
Running "main" (2 tests)...
Completed "foo_suite": 1 pass in 14ms.
Completed "c_list_suite": 2 passes in 21ms.
Completed "main": 2 passes in 35ms.
The output shows that by adding an element into the list returned a c_list_node_count of 1. We were able to test this by inserting a constraint on the unit test, which ultimately fails if the given inputs are not successful when ran. Here is a list of constraints you can use in your unit tests:
Constraint | Passes if actual value/expression… |
---|---|
is_true | evaluates to true |
is_false | evaluates to false |
is_null | equals null |
is_non_null | is a non null value |
is_equal_to(value) | '== value' |
is_not_equal_to(value) | '!= value' |
is_greater_than(value) | '> value' |
is_less_than(value) | '< value' |
is_equal_to_contents_of(pointer, size) | matches the data pointed to by pointer to a size of size bytes |