Unit Testing with AVR Studio and AVR-GCC
When developing embedded C applications for 8-bit AVR microcontrollers, I found the lack of a unit test framework troublesome. Especially as unit testing now forms a standard part of our C-sharp and Java development processes.
There are a number of C and C++ unit testing frameworks available but nothing geared up for running in the target environment of an 8-bit AVR.
The “framework” produced here is a lightweight starting point that can be enhanced to provide output in any form useful to the user. It is currently used in the AVR Simulator as part of the AVR Studio 5 environment, but there’s no reason why it couldn’t be run on the target device with test result output streamed over a USART connection.
Init levels
Adding the tests in JUnit etc allows you to scan the classes and run them all automatically. We can’t quite do the same in C. However, what we can do is to make use of the .init levels and have a function for each test automatically called on system start-up. These functions will then automatically add each test to the test suite.
Simulator
By running the tests in the AVR simulator, we can use the Watch tools to view the test results by putting breakpoints after each test is run, or after all tests have run. Monitor the m_Test_activeTest for the current test data, and m_Test_result for the final results.
The Code
test.h
#ifndef TEST_H
#define TEST_H
#include <inttypes.h>
// typedefs
typedef enum TestResult
{
NOT_RUN = 0,
SUCCESS,
FAILURE
} TestResult;
typedef struct Test_TestHolder
{
char * name;
void (*testFunction)(void);
char * file;
int line;
TestResult testResult;
struct Test_TestHolder* next;
} Test_TestHolder;
// Initialise the test framework
void Test_add(Test_TestHolder* test);
void Test_assertTrueLog(uint8_t condition, uint16_t lineNumber);
void Test_assertEqualLog(uint16_t expected, uint16_t actual,
uint16_t lineNumber);
void Test_runall(void);
#if __TEST__
void Test_init(void) __attribute__ ((naked))
__attribute__ ((section (".init7")));
#define Test_run() {Test_runall(); for(;;); }
#define Test_test(MODULE, NAME)
void MODULE##_test_##NAME(void);
void MODULE##_appendtest_##NAME(void)
__attribute__ ((naked))
__attribute__ ((section (".init8")));
Test_TestHolder m_##MODULE_test_##NAME = { #NAME,
MODULE##_test_##NAME, __FILE__, 0, NOT_RUN };
void MODULE##_appendtest_##NAME(void) {
Test_add( &m_##MODULE_test_##NAME ); };
void MODULE##_test_##NAME(void)
#else
void Test_init(void);
#define Test_run()
#define Test_test(MODULE, NAME)
void MODULE##_test_##NAME(void);
void MODULE##_test_##NAME(void)
#endif
#define Test_assertTrue(condition)
Test_assertTrueLog((condition), __LINE__);
if (!(condition)) {
return;
}
#define Test_assertEquals(expected, actual)
Test_assertEqualLog((expected), (actual), __LINE__);
if ((expected) != (actual)) {
return;
}
#endif /* TEST_H */
test.c
#include "test.h"
#include <stdlib.h>
#include <string.h>
Test_TestHolder* m_Test_head;
Test_TestHolder* m_Test_activeTest;
typedef struct Test_ResultType
{
uint16_t totalTests;
uint16_t successCount;
uint16_t failureCount;
} Test_ResultType;
Test_ResultType m_Test_result;
// Initialise the test framework
void Test_init(void)
{
m_Test_head = NULL;
m_Test_activeTest = NULL;
m_Test_result.totalTests = 0;
m_Test_result.successCount = 0;
m_Test_result.failureCount = 0;
}
void Test_add(Test_TestHolder* test)
{
// Put to front of chain
test->next = m_Test_head;
m_Test_head = test;
}
void Test_assertTrueLog(uint8_t condition, uint16_t lineNumber)
{
// We have the active test
if( !(condition) )
{
m_Test_activeTest->testResult = FAILURE;
m_Test_activeTest->line = lineNumber;
}
}
void Test_assertEqualLog(uint16_t expected, uint16_t actual,
uint16_t lineNumber)
{
if( expected != actual )
{
m_Test_activeTest->testResult = FAILURE;
m_Test_activeTest->line = lineNumber;
}
}
// Run through all the tests
void Test_runall(void)
{
// Reset counts
m_Test_result.totalTests = 0;
m_Test_result.successCount = 0;
m_Test_result.failureCount = 0;
// Reset status of all
m_Test_activeTest = m_Test_head;
while( m_Test_activeTest != NULL )
{
m_Test_result.totalTests++;
m_Test_activeTest->testResult = NOT_RUN;
m_Test_activeTest->line = 0;
// next in the chain
m_Test_activeTest = m_Test_activeTest->next;
}
// Now execute the tests
m_Test_activeTest = m_Test_head;
while( m_Test_activeTest != NULL )
{
m_Test_activeTest->testFunction();
if( m_Test_activeTest->testResult == NOT_RUN )
{
m_Test_activeTest->testResult = SUCCESS;
m_Test_result.successCount++;
}
else
{
m_Test_result.failureCount++;
}
asm("nop");
// next in the chain
m_Test_activeTest = m_Test_activeTest->next;
}
// Get the results
asm("nop");
}
// Examples
/*
Test_test(Test, testWillFail)
{
Test_assertEquals(1, 0);
}
Test_test(Test, testWillPass)
{
Test_assertTrue(1);
}
*/
main.c
#include "test.h"
int main(void)
{
Test_run(); // System code
}