Unit Test How? Faking Changing Registers
This is part of an ongoing series of articles about using Unity to Unit Test C applications, often for Embedded Applications. If you're looking for other tips, be sure to check out the others here.
Today we're going to discuss how to fake changing registers. How do we wait on a flag to be set during a test. How do we read from a single-address queue register? Many architectures have registers that have some complex behavior, and it's often not obvious how to test it at first.
We're going to build on the foundation of what we already know about testing registers. If you would like a refresher, you can find it here.
Let's start with something we already know how to test, then build up a couple of more challenging examples. Here is a simple function:
int SimpleFunc(void)
{
if (PORTA_DIR & 0x1000)
if (PORTA_OUT & 0x1000)
return 1;
return 0;
}
It might have tests that look something like this:
void test_SimpleFunc_should_return_0_IfWrongDirection(void)
{
PORTA_DIR = 0x2000;
PORTA_OUT = 0x1000;
TEST_ASSERT_EQUAL(0, SimpleFunc());
}
void test_SimpleFunc_should_return_0_IfWrongValue(void)
{
PORTA_DIR = 0x1000;
PORTA_OUT = 0x0001;
TEST_ASSERT_EQUAL(0, SimpleFunc());
}
void test_SimpleFunc_should_return_1_IfBothRight(void)
{
PORTA_DIR = 0x1000;
PORTA_OUT = 0x1000;
TEST_ASSERT_EQUAL(1, SimpleFunc());
}
Tests like this are easy to set up, because they only involve one read of each register. Most often, this will get us by... but occasionally, we need to check the same register more than once. We could break it into multiple functions, but sometimes they just logically belong together. So what do we do then?
Let's start with an example.
int FetchMessage(char* data, int len)
{
int i;
if (len > 8)
return ERR_TOO_LONG;
if (len < 1)
return ERR_TOO_SHORT;
if (!(COMM_STATUS & COMM_OK_TO_RECV_FLAG))
return ERR_CANT_RECV;
for (i=0; i < len; i++)
{
if (COMM_STATUS & COMM_BUFFER_EMPTY)
return i;
data[i] = COMM_DATA_IN & 0x00FF;
}
return len;
}
There are a couple of things to notice about this function. First, we read from COMM_STATUS at the beginning, to make sure we are configured to receive data, and then for each incoming byte to verify our peripheral's queue isn't empty.
The second thing to notice is that we receive data through reading the COMM_DATA_IN register... which means we read it multiple times.
First, we start by writing the tests that are reachable without jumping through any hoops at all. Often, you'll find that you can test a lot more than you think you can, just by being careful about how you preload your registers. These tests are a bit more brittle than usual, but they'll get you by in many cases:
char actual[8];
void setUp(void)
{
int i;
for (i=0; i < 8; i++)
actual[i] = 0;
}
void test_FetchMessage_should_ReturnErrorIfNotOkToRecieve(void)
{
COMM_STATUS = 0;
TEST_ASSERT_EQUAL(ERR_CANT_RECV, FetchMessage(actual, 8);
}
void test_FetchMessage_should_ReturnErrorIfBufferEmpty(void)
{
COMM_STATUS = COMM_OK_TO_RECV_FLAG | COMM_BUFFER_EMPTY;
TEST_ASSERT_EQUAL(ERR_CANT_RECV, FetchMessage(actual, 8);
TEST_ASSERT_EQUAL(0, actual[0]);
}
void test_FetchMessage_should_GetASeriesOfData(void)
{
char* expected = 'AAAAAAAA';
COMM_STATUS = COMM_OK_TO_RECV_FLAG; //and NOT COMM_BUFFER_EMPTY
COMM_DATA_IN = 'A';
TEST_ASSERT_EQUAL(8, FetchMessage(actual, 8) );
TEST_ASSERT_EQUAL_STRING(expected, actual);
}
So, we can get quite a bit of work done by not changing our registers mid test, but we're stuck making assumptions. For example, we had to assume there is always data ready to get any data, and that the data is always the same value ('A'). Often, these assumptions are going to be an acceptable tradeoff. When they're not, it's often an indicator that we could do a cleaner job of deciding what work gets done where.
Occasionally, though, we might want to more thoroughly test such a feature and we're not in control of the implementation. In these cases, we can create more complex "register" definitions. Consider this:
#define RETURN_NEXT(vals, index) \
((vals == NULL) ? 0 : \
((--index > 0) ? vals[index] : vals[0]) \
)
EXTERN int* COMM_STATUS_vals= NULL;
EXTERN intCOMM_STATUS_index = 0;
#define COMM_STATUS RETURN_NEXT( \
COMM_STATUS_vals, \
COMM_STATUS_index \
)
EXTERN int* COMM_DATA_IN_vals= NULL;
EXTERN intCOMM_DATA_IN_index = 0;
#define COMM_DATA_IN RETURN_NEXT( \
COMM_DATA_IN_vals, \
COMM_DATA_IN_index \
)
These "registers" are now set up to handle a queue of data that you set up ahead of time. For example, if we wanted to create a test that verified we could receive three bytes of valid data, and then return, it might look like this:
void test_FetchMessage_should_GetDataUntilEmpty(void)
{
char* expected = 'AAAAAAAA';
int statuses[] = {
COMM_OK_TO_RECV_FLAG,
0,
0,
0,
COMM_BUFFER_EMPTY,
};
int reads[] = { 'H', 'i', '!' };
COMM_STATUS_val= statuses;
COMM_STATUS_index= sizeof(statuses);
COMM_DATA_IN_val = reads;
COMM_DATA_IN_index = sizeof(reads);
TEST_ASSERT_EQUAL(3, FetchMessage(actual, 8) );
TEST_ASSERT_EQUAL_STRING(expected, actual);
}
So this is much more flexible... but with this much complexity, we clearly don't want to test things this way regularly. We'll save this one for special occasions.
Happy Coding!