Olson CloudWorks 🚀

How can I unit test Arduino code

September 19, 2026

How can I unit test Arduino code

The Arduino platform has revolutionized the world of DIY electronics and embedded systems. Its ease of use and vast community support make it an excellent choice for hobbyists and professionals alike. However, as projects grow in complexity, ensuring code reliability becomes paramount. This is where unit testing comes in. Many developers wonder, “How can I unit test Arduino code?” It’s a critical question because robust testing can save you from costly hardware failures, unexpected behavior in the field, and countless hours of debugging. Unit testing involves isolating and testing individual components or functions of your Arduino code to verify they perform as expected. By implementing effective unit tests, you can catch bugs early, improve code maintainability, and build more reliable Arduino projects. The process may seem daunting initially, but with the right tools and techniques, it can become an integral part of your Arduino development workflow, leading to more stable and dependable embedded systems.

Understanding the Importance of Unit Testing for Arduino

Unit testing is a software development practice where individual units or components of a program are tested in isolation. In the context of Arduino, this means testing individual functions or classes to ensure they behave correctly. Why is this important? Arduino projects often interact with the real world through sensors and actuators. A bug in your code could lead to unexpected behavior, potentially damaging hardware or causing incorrect readings. Unit tests provide a safety net, allowing you to verify that your code functions as intended before deploying it to the Arduino board. By implementing unit tests, you gain confidence in your code’s reliability and reduce the risk of runtime errors. Moreover, unit tests act as living documentation, clearly illustrating the expected behavior of each component.

Consider a simple example: a function that reads data from a temperature sensor. Without unit tests, you might only discover a bug when the sensor is already deployed and providing incorrect readings. However, with unit tests, you can simulate different sensor inputs and verify that the function returns the correct temperature values under various conditions. “Automated testing is essential for ensuring the reliability and maintainability of software systems,” states Kent Beck, a pioneer in agile software development. Source: Test-Driven Development by Kent Beck. This principle applies equally to Arduino projects, where reliability is often crucial.

Furthermore, unit testing simplifies the refactoring process. As your project evolves, you may need to modify existing code. With a comprehensive suite of unit tests, you can confidently make changes, knowing that the tests will catch any unintended side effects. This allows you to maintain a clean and well-structured codebase, making it easier to add new features and fix bugs in the future. Embracing unit testing is about building better, more reliable, and more maintainable Arduino projects.

Setting Up Your Arduino Unit Testing Environment

Before you can start writing unit tests, you need to set up your development environment. Several tools and frameworks are available to facilitate unit testing for Arduino. One popular option is the Arduino IDE combined with a testing framework like ArduinoUnit or PlatformIO. PlatformIO, in particular, offers a comprehensive development environment with built-in support for unit testing and dependency management. Choosing the right toolset depends on your project’s complexity and your personal preferences. However, the underlying principles remain the same: you need a way to write test cases, execute them, and verify the results.

To set up a basic unit testing environment using ArduinoUnit, you would typically:

  1. Install the Arduino IDE.
  2. Download the ArduinoUnit library from GitHub.
  3. Include the ArduinoUnit library in your Arduino project.
  4. Write your test cases using the ArduinoUnit macros (e.g., assertEquals, assertTrue).
  5. Compile and run the tests on your Arduino board or a simulator.

This approach allows you to write tests directly within the Arduino IDE and execute them on the target hardware. However, for more complex projects, PlatformIO might be a better choice due to its advanced features and better integration with continuous integration systems. Alternatively, consider using a simulator like Wokwi. Wokwi lets you simulate your Arduino projects in a web browser, making it easy to run unit tests without needing physical hardware. This can significantly speed up the development process and make it easier to test different scenarios. Whichever environment you choose, it’s crucial to familiarize yourself with the testing framework’s syntax and features. Proper setup is the foundation for effective unit testing.

Writing Effective Unit Tests for Arduino Code

Writing effective unit tests requires a strategic approach. Start by identifying the critical components or functions in your Arduino code that need to be tested. Focus on functions that perform calculations, interact with sensors or actuators, or handle complex logic. For each function, write a set of test cases that cover different scenarios and edge cases. Aim for comprehensive coverage, ensuring that all possible code paths are tested. A well-designed unit test should be atomic, meaning it tests only one specific aspect of the function. It should also be independent, meaning it doesn’t rely on the state of other components.

For example, if you have a function that calculates the average of an array of numbers, you might write test cases to cover:

  • An array with positive numbers.
  • An array with negative numbers.
  • An empty array.
  • An array with a single element.

Each test case should assert that the function returns the correct average value for the given input. Use descriptive test names to clearly indicate what each test is verifying. This makes it easier to understand the test results and identify any failures. Here is an example of a test case using ArduinoUnit: test(“Average of positive numbers”, []() { assertEquals(5, average({2, 4, 6, 8})); }); A key aspect of writing effective unit tests is to use mocks and stubs to isolate the component under test. Mocks are objects that simulate the behavior of external dependencies, such as sensors or actuators. Stubs are simplified implementations of these dependencies that return predefined values. By using mocks and stubs, you can control the inputs to your function and verify that it behaves correctly regardless of the external environment. “The goal of unit testing is to isolate each part of the program and show that the individual parts are correct,” says Robert C. Martin, author of Clean Code. Source: Clean Code by Robert C. Martin. This isolation is crucial for ensuring that your unit tests are reliable and repeatable. This paragraph is optimized to be a featured snippet.

Advanced Unit Testing Techniques for Arduino

Once you’ve mastered the basics of unit testing, you can explore more advanced techniques to improve the quality and coverage of your tests. One such technique is test-driven development (TDD), where you write the unit tests before writing the actual code. This forces you to think about the function’s behavior and requirements upfront, leading to a more well-designed and testable implementation. TDD can be particularly useful for complex algorithms or functions with multiple edge cases.

Another advanced technique is using code coverage tools to measure the percentage of your code that is covered by unit tests. Code coverage tools can identify areas of your code that are not being tested, allowing you to focus your testing efforts on those areas. Aim for high code coverage to ensure that your unit tests provide comprehensive protection against bugs. However, remember that code coverage is not a substitute for well-designed test cases. It’s possible to achieve high code coverage with poorly written tests that don’t effectively verify the function’s behavior.

Consider these key points for advanced unit testing:

  • Use mocks and stubs extensively to isolate components.
  • Employ test-driven development (TDD) for complex functionality.

And remember to take these additional points into account: - Utilize code coverage tools to identify gaps in testing.

  • Integrate unit tests into your continuous integration (CI) workflow.

Integrating unit tests into your continuous integration (CI) workflow is another crucial step. CI systems automatically build and test your code whenever changes are made, providing instant feedback on the impact of those changes. This helps to catch bugs early and prevent them from making their way into production. Incorporating unit testing into your CI pipeline ensures that your code is always thoroughly tested and that any regressions are quickly detected. Learn more about continuous integration.
Infographic here
FAQ: Unit Testing Arduino Code

**Q: Can I unit test Arduino code without an actual Arduino board?**
A: Yes, you can use simulators like Wokwi or frameworks that allow you to run tests on your computer without hardware.
**Q: What are the benefits of unit testing Arduino code?**
A: Early bug detection, improved code reliability, easier maintenance, and increased confidence in your code's correctness.
**Q: What is the best framework for unit testing Arduino code?**
A: ArduinoUnit and PlatformIO are popular choices, each with its own strengths and weaknesses. Choose the one that best suits your project's needs.
**Q: How do I mock sensor readings for unit tests?**
A: Create mock objects that simulate the sensor's behavior and return predefined values during the tests.
Building reliable Arduino projects hinges on robust testing strategies, and unit testing is a cornerstone of that process. By embracing these techniques, you not only improve the quality of your code but also accelerate development cycles and minimize the risk of costly errors. Investing time in learning and implementing unit testing will pay dividends in the long run, resulting in more stable, maintainable, and trustworthy Arduino applications. Don't wait for bugs to surface in the field; start unit testing your Arduino code today and experience the benefits firsthand. Consider exploring advanced topics like integration testing and continuous integration to further enhance your Arduino development workflow. **Question & Answer :** I'd like to be able to unit test my Arduino code. Ideally, I would be able to run any tests without having to upload the code to the Arduino. What tools or libraries can help me with this?

There is an Arduino emulator in development which could be useful, but it doesn’t yet seem to be ready for use.

AVR Studio from Atmel contains a chip simulator which could be useful, but I can’t see how I would use it in conjunction with the Arduino IDE.

Don’t Run Unit Tests on the Arduino Device or Emulator

The case against microcontroller Device/Emulator/Sim-based tests

There’s a lot of discussion about what unit test means and I’m not really trying to make an argument about that here. This post is not telling you to avoid all practical testing on your ultimate target hardware. I am trying to make a point about optimizing your development feedback cycle by eliminating your target hardware from your most mundane and frequent tests. The units under test are assumed to be much smaller than the whole project.

The purpose of unit testing is to test the quality of your own code. Unit tests should generally never test the functionality of factors outside of your control.

Think about it this way: Even if you were to test functionality of the Arduino library, the microcontroller hardware, or an emulator, it is absolutely impossible for such test results to tell you anything about the quality of your own work. Hence, it is far more valuable and efficient to write unit tests that do not run on the target device (or emulator).

Frequent testing on your target hardware has a painfully slow cycle:

  1. Tweak your code
  2. Compile and upload to Arduino device
  3. Observe behavior and guess whether your code is doing what you expect
  4. Repeat

Step 3 is particularly nasty if you expect to get diagnostic messages via serial port but your project itself needs to use your Arduino’s only hardware serial port. If you were thinking that the SoftwareSerial library might help, you should know that doing so is likely to disrupt any functionality that requires accurate timing like generating other signals at the same time. This problem has happened to me.

Again, if you were to test your sketch using an emulator and your time-critical routines ran perfectly until you uploaded to the actual Arduino, then the only lesson you’re going to learn is that the emulator is flawed–and knowing this still reveals nothing about the quality of your own work.

If it’s silly to test on the device or emulator, what should I do?

You’re probably using a computer to work on your Arduino project. That computer is orders of magnitudes faster than the microcontroller. Write the tests to build and run on your computer.

Remember, the behavior of the Arduino library and microcontroller should be assumed to be either correct or at least consistently incorrect.

When your tests produce output contrary to your expectations, then you likely have a flaw in your code that was tested. If your test output matches your expectations, but the program does not behave correctly when you upload it to the Arduino, then you know that your tests were based on incorrect assumptions and you likely have a flawed test. In either case, you will have been given real insights on what your next code changes should be. The quality of your feedback is improved from “something is broken” to “this specific code is broken”.

How to Build and Run Tests on Your PC

The first thing you need to do is identify your testing goals. Think about what parts of your own code you want to test and then make sure to construct your program in such a way that you can isolate discrete parts for testing.

If the parts that you want to test call any Arduino functions, you will need to provide mock-up replacements in your test program. This is much less work than it seems. Your mock-ups don’t have to actually do anything but providing predictable input and output for your tests.

Any of your own code that you intend to test needs to exist in source files other than the .pde sketch. Don’t worry, your sketch will still compile even with some source code outside of the sketch. When you really get down to it, little more than your program’s normal entry point should be defined in the sketch file.

All that remains is to write the actual tests and then compile it using your favorite C++ compiler! This is probably best illustrated with a real world example.

An actual working example

One of my pet projects found here has some simple tests that run on the PC. For this answer submission, I’ll just go over how I mocked-up some of Arduino library functions and the tests I wrote to test those mock-ups. This is not contrary to what I said before about not testing other people’s code because I was the one who wrote the mock-ups. I wanted to be very certain that my mock-ups were correct.

Source of mock_arduino.cpp, which contains code that duplicates some support functionality provided by the Arduino library:

#include <sys/timeb.h> #include "mock_arduino.h" timeb t_start; unsigned long millis() { timeb t_now; ftime(&t_now); return (t_now.time - t_start.time) * 1000 + (t_now.millitm - t_start.millitm); } void delay( unsigned long ms ) { unsigned long start = millis(); while(millis() - start < ms){} } void initialize_mock_arduino() { ftime(&t_start); } 

I use the following mock-up to produce readable output when my code writes binary data to the hardware serial device.

fake_serial.h

#include <iostream> class FakeSerial { public: void begin(unsigned long); void end(); size_t write(const unsigned char*, size_t); }; extern FakeSerial Serial; 

fake_serial.cpp

#include <cstring> #include <iostream> #include <iomanip> #include "fake_serial.h" void FakeSerial::begin(unsigned long speed) { return; } void FakeSerial::end() { return; } size_t FakeSerial::write( const unsigned char buf[], size_t size ) { using namespace std; ios_base::fmtflags oldFlags = cout.flags(); streamsize oldPrec = cout.precision(); char oldFill = cout.fill(); cout << "Serial::write: "; cout << internal << setfill('0'); for( unsigned int i = 0; i < size; i++ ){ cout << setw(2) << hex << (unsigned int)buf[i] << " "; } cout << endl; cout.flags(oldFlags); cout.precision(oldPrec); cout.fill(oldFill); return size; } FakeSerial Serial; 

and finally, the actual test program:

#include "mock_arduino.h" using namespace std; void millis_test() { unsigned long start = millis(); cout << "millis() test start: " << start << endl; while( millis() - start < 10000 ) { cout << millis() << endl; sleep(1); } unsigned long end = millis(); cout << "End of test - duration: " << end - start << "ms" << endl; } void delay_test() { unsigned long start = millis(); cout << "delay() test start: " << start << endl; while( millis() - start < 10000 ) { cout << millis() << endl; delay(250); } unsigned long end = millis(); cout << "End of test - duration: " << end - start << "ms" << endl; } void run_tests() { millis_test(); delay_test(); } int main(int argc, char **argv){ initialize_mock_arduino(); run_tests(); } 

This post is long enough, so please refer to my project on GitHub to see some more test cases in action. I keep my works-in-progress in branches other than master, so check those branches for extra tests, too.

I chose to write my own lightweight test routines, but more robust unit-test frameworks like CppUnit are also available.