DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Best Practices for Writing Unit Tests: A Comprehensive Guide
  • Two Cool Java Frameworks You Probably Don’t Need
  • Testing Serverless Functions
  • Why I Prefer Flutter Over React Native for App Development

Trending

  • Data Quality: A Novel Perspective for 2025
  • Why Database Migrations Take Months and How to Speed Them Up
  • How Can Developers Drive Innovation by Combining IoT and AI?
  • Comprehensive Guide to Property-Based Testing in Go: Principles and Implementation
  1. DZone
  2. Coding
  3. Frameworks
  4. uCUnit: a Unit Test Framework for Microcontrollers

uCUnit: a Unit Test Framework for Microcontrollers

Want to learn more about unit testing for microcontrollers? Check out this post to learn more about uCUnit and unit testing for embedded development.

By 
Erich Styger user avatar
Erich Styger
·
Oct. 31, 18 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
8.0K Views

Join the DZone community and get the full member experience.

Join For Free

Unit testing is a common practice for host development. But for embedded development, this still seems mostly a ‘blank’ area. This is mostly because embedded engineers are not used to unit testing or because the usual framework for unit testing requires too many resources on an embedded target.

What I have used is the μCUnit framework, which is a small and easy-to-use framework, targeting small microcontroller applications.

uCUnit

The framework is very simple: two header files and a .c file:

uCUnit Framework Files

Today, we will use the original ones from the uCUnit GitHub site or the ones I have modified from GitHub to be used with the MCUXpresso SDK and IDE.

The concept is that a unit test includes the uCunit.h header file, which provides test macros.

A #define in the header file configures the output as verbose or normal:

UCUNIT_MODE_NORMAL or UCUNIT_MODE_VERBOSE

UCUNIT_MODE_NORMAL or UCUNIT_MODE_VERBOSE

System.c and System.h represents the connection to the system, which is basically used for startup, shutdown, and printing the test results to a console. Below shows an implementation using the  printf() method to write the output, but this could be replaced by any writing routine or extended to log text on an SD card.

/* Stub: Transmit a string to the host/debugger/simulator */
void System_WriteString(char * msg) {
    PRINTF(msg);
}

void System_WriteInt(int n) {
    PRINTF("%d", n);
}


Framework Overview

First, I have to include the unit test framework header file:

#include "uCUnit.h"


Then, I have to initialize the framework with:

UCUNIT_Init(); /* initialize framework */


One more test case is wrapped with a UCUNIT_TestcaseBegin()  and  UCUNIT_TestcaseEnd():

UCUNIT_TestcaseBegin("Crazy Scientist");
/* test cases ... */
UCUNIT_TestcaseEnd();


To write a summary at the end, use

UCUNIT_WriteSummary();


And if the system shall be shut down, use a:

UCUNIT_Shutdown();


Tests

The framework provides multiple testing methods, such as:

UCUNIT_CheckIsEqual(x, 0); /* check if x == 0 */
UCUNIT_CheckIsInRange(x, 0, 10); /* check 0 <= x <= 10 */
UCUNIT_CheckIsBitSet(x, 7); /* check if bit 7 set */
UCUNIT_CheckIsBitClear(x, 7); /* check if bit 7 cleared */
UCUNIT_CheckIs8Bit(x); /* check if not larger then 8 bit */
UCUNIT_CheckIs16Bit(x); /* check if not larger then 16 bit */
UCUNIT_CheckIs32Bit(x); /* check if not larger then 32 bit */
UCUNIT_CheckIsNull(p); /* check if p == NULL */
UCUNIT_CheckIsNotNull(s); /* check if p != NULL */
UCUNIT_Check((*s)==’\0’, "Missing termination", "s"); /* generic check: condition, msg, args */


This is explained best with a few examples. Let's take a look.

Example: Crazy Scientist

Below is a ‘crazyScientist’ function that combines different materials:

typedef enum {
    Unknown,  /* first, generic item */
    Hydrogen, /* H */
    Helium,   /* He */
    Oxygen,   /* O */
    Oxygen2,  /* O2 */
    Water,    /* H2O */
    ChemLast  /* last, sentinel */
} Chem_t;

Chem_t crazyScientist(Chem_t a, Chem_t b) {
    if (a==Oxygen && b==Oxygen) {
        return Oxygen2;
    }
    if (a==Hydrogen && b==Oxygen2) {
        return Water;
    }
    return Unknown;
}


A test for this could look like this:

void Test(void) {
  Chem_t res;
  UCUNIT_Init(); /* initialize framework */

  UCUNIT_TestcaseBegin("Crazy Scientist");
  res = crazyScientist(Oxygen, Oxygen);
  UCUNIT_CheckIsEqual(res, Oxygen2);
  UCUNIT_CheckIsEqual(Unknown, crazyScientist(Water, Helium));
  UCUNIT_CheckIsEqual(Water, crazyScientist(Hydrogen, Oxygen2));
  UCUNIT_CheckIsEqual(Water, crazyScientist(Oxygen2, Hydrogen));
  UCUNIT_CheckIsInRange(crazyScientist(Unknown, Unknown), Unknown, ChemLast);
  UCUNIT_TestcaseEnd();

  /* finish all the tests */
  UCUNIT_WriteSummary();
  UCUNIT_Shutdown();
}


With the different checks, we can verify if the function is doing what we expect. It produces the following output:

======================================
Crazy Scientist
======================================
../source/Application.c:60: passed:IsEqual(res,Oxygen2)
../source/Application.c:61: passed:IsEqual(Unknown,crazyScientist(Water, Helium))
../source/Application.c:62: passed:IsEqual(Water,crazyScientist(Hydrogen, Oxygen2))
../source/Application.c:63: failed:IsEqual(Water,crazyScientist(Oxygen2, Hydrogen))
../source/Application.c:64: passed:IsInRange(crazyScientist(Unknown, Unknown),Unknown,ChemLast)
======================================
../source/Application.c:65: failed:EndTestcase()
======================================

**************************************
Testcases: failed: 1
           passed: 0
Checks:    failed: 1
           passed: 4
**************************************
System shutdown.

I recommend writing the unit tests *before* doing the implementation, because this way, it lets me consider all the different corner cases and refine the requirements.

The above output is with UCUNIT_MODE_VERBOSE set. Using UCUNIT_MODE_NORMAL, it uses a more compact format and prints the failed tests only:

======================================
Crazy Scientist
======================================
../source/Application.c:63: failed:IsEqual(Water,crazyScientist(Oxygen2, Hydrogen))
======================================
../source/Application.c:65: failed:EndTestcase()
======================================

**************************************
Testcases: failed: 1
           passed: 0
Checks:    failed: 1
           passed: 4
**************************************
System shutdown.


Trace Points

In the above example, we were only testing from the outside what the function does.
How to check that the following function can be tested and, indeed, checks for the division by zero cases? Let's take a look:

int checkedDivide(int a, int b) {
    if (b==0) {
        PRINTF("division by zero is not defined!\n");
        return 0;
    }
    return a/b;
}


To check that the if() condition has been really entered, I can add a tracepoint. The number of tracepoints is configured in μCUnit.h with:

/**
 * Max. number of checkpoints. This may depend on your application
 * or limited by your RAM.
 */
#define UCUNIT_MAX_TRACEPOINTS 16


With;

UCUNIT_ResetTracepointCoverage();


I can reset the tracepoints by marking the execution of a tracepoint with an id (which is in the range 0..UCUNIT_MAX_TRACEPOINTS-1):

UCUNIT_Tracepoint(id);


With:

UCUNIT_CheckTracepointCoverage(0);


Also, I can check if a given tracepoint has been touched. Below the function to be tested instrumented with a tracepoint:

int checkedDivide(int a, int b) {
    if (b==0) {
        UCUNIT_Tracepoint(0); /* mark trace point */
        PRINTF("division by zero is not defined!\n");
        return 0;
    }
    return a/b;
}


The corresponding unit test code:

UCUNIT_TestcaseBegin("Checked Divide");
UCUNIT_CheckIsEqual(100/5, checkedDivide(100,5));
UCUNIT_ResetTracepointCoverage(); /* start tracking */
UCUNIT_CheckIsEqual(0, checkedDivide(1024,0));
UCUNIT_CheckTracepointCoverage(0); /* check coverage of point 0 */
UCUNIT_TestcaseEnd();


Which then produces:

======================================
Checked Divide
======================================
../source/Application.c:69: passed:IsEqual(100/5,checkedDivide(100,5))
division by zero is not defined!
../source/Application.c:71: passed:IsEqual(0,checkedDivide(1024,0))
../source/Application.c:72: passed:TracepointCoverage(1)


String Test

There are many other ways to use checks and have user configured checks and messages. Below is an example of a function to test:

char *endOfString(char *str) {
  if (str==NULL) {
    return NULL;
  }
  while(*str!='\0') {
    str++;
  }
  return str;
}


With the following test code:

UCUNIT_TestcaseBegin("Strings");
UCUNIT_CheckIsNull(endOfString(NULL));
str = endOfString("abc");
UCUNIT_Check(
    (str!=NULL), /* condition to check */
    "string shall be not NULL", /* message */
    "str" /* argument as string */
    );
UCUNIT_CheckIsEqual('\0', *endOfString(""));
UCUNIT_CheckIsEqual('\0', *endOfString("hello"));
str = endOfString("world");
UCUNIT_CheckIsNotNull(str);
UCUNIT_CheckIsEqual('\0', *str);
UCUNIT_TestcaseEnd();


This produces:

======================================
Strings
======================================
../source/Application.c:76: passed:IsNull(endOfString(NULL))
../source/Application.c:82: passed:string shall be not NULL(str)
../source/Application.c:83: passed:IsEqual('\0',*endOfString(""))
../source/Application.c:84: passed:IsEqual('\0',*endOfString("hello"))
../source/Application.c:86: passed:IsNotNull(str)
../source/Application.c:87: passed:IsEqual('\0',*str)


Summary

μCUnit is a very simple yet powerful unit testing framework for embedded devices and microcontrollers. It is easy to use and only requires minimal resources and helps to increase the quality of embedded software with automated unit tests. I hope you find it useful, too!

Happy testing!

Helpful Links

  • μCUnit web page: http://www.ucunit.org/
  • μCUnit Documentation: http://www.ucunit.org/_documentation.html
  • μCUnit Github site: https://github.com/ucunit/ucunit
  • μCUnit example usage: https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/MCUXpresso/FRDM-K64F/FRDM-K64F_uCUnit
  • Port of μCUnit for MCUXpresso: https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/MCUXpresso/FRDM-K64F/FRDM-K64F_uCUnit/uCUnit
unit test Framework

Published at DZone with permission of Erich Styger, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Best Practices for Writing Unit Tests: A Comprehensive Guide
  • Two Cool Java Frameworks You Probably Don’t Need
  • Testing Serverless Functions
  • Why I Prefer Flutter Over React Native for App Development

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: