Language: EN

arduino-simulator-unit-testing

How to perform unit tests on Arduino with Arduino Simulator

Today we are going to see how to perform automated tests on Arduino, and for that, I share one of my “secret weapons”, the Arduino Simulator library.

Suppose you have a machine that you want to put into production. This machine has a keypad, a user menu, an emergency stop button, an alarm speaker, sensors … and who knows what else.

Naturally, you will want to test that it works correctly. Every time you make a change, you’re not going to upload the code to the real machine and start pressing buttons and checking that everything works, right? Isn’t that right?!?

arduino-simulation-meme

No, what you would like to do is perform a series of automated tests to verify that the code works correctly. This is where Arduino Simulator comes into play.

Schedule

Let’s imagine that your machine has to do certain things when certain events occur. For example, if we press the emergency button, it has to stop and turn on the alarm.

We will call this a use case. With Arduino Simulator, we can create a use case using a Schedule, which contains the actions that will occur at each moment.

TEST_METHOD(MY_COOL_EXAMPLE_TEST)
{
   SIMULATION_STEP_US = 50;
   AT_MILLIS(2000, []() { digitalWrite(0, HIGH); });
   AT_MILLIS(5000, []() { digitalWrite(0, LOW); });
   AT_MILLIS(8000, []() { SIMULATE_INTERRUPT(0, HIGH); });
   AT_MILLIS(12000, []() { SAVE_STATE(); });

   EACH_MILLIS(1000, []() { digitalWrite(1, digitalRead(1) == LOW ? HIGH : LOW); });

   THEN([]() { analogWrite(0, 250); });

   for (size_t pwm = 250; pwm > 50; pwm -= 50)
   {
      THEN_AFTER_MILLIS(500, [pwm]() { ADC_Input[0] = pwm; });
   }

   simulate_seconds(50);

   Assert::IsTrue(Gpio_Status[0] == LOW);
   Assert::IsTrue(Gpio_Status[1] == LOW);
   Assert::IsTrue(Gpio_Status[2] == HIGH);
   Assert::IsTrue(PWM_Output[0] > 200);
   Assert::IsTrue(ADC_Input[0] <= 100);
}

To do this, we can use

  • AT_MILLIS, AT_MICROS
  • THEN
  • THEN_AFTER_MILLIS, THEN_AFTER_MICROS
  • EACH_MILLIS, EACH_MICROS

Testing

You can use your IDE’s Test environment to generate various unit tests or complete use cases.

arduino-simulation-tests

When you, or someone on the team, modifies the code, running all the tests is as simple as pressing a button and verifying that the machine’s behavior is still correct.

Debug

Another advantage of simulating your machine is that you can use your IDE’s debug to check the state of the machine.

arduino-simulation-debug

No more using Serial.println everywhere for debugging! Now you can easily find errors, add breakpoints, and debug properly.

Arduino Simulator

Arduino Simulator is a library that allows us to test Arduino’s C++ code from your favorite IDE, such as Visual Studio or Visual Studio Code.

It is not, nor does it intend to be, nor will it ever be, a simulation of Arduino’s hardware. It is designed to simulate your code, not your hardware.

For this, the “machine” that simulates has 16 GPIO, 8 PWM outputs, 8 ADC inputs, and 2 interruptions. Although you can easily expand it by changing the code.

In other words, if your machine starts writing registers and doing things like PORTy = 0x00, Arduino Simulator will send you away! And it does well!

In short, in order to test your code, it must be testable. If you don’t know how to make your code testable, stop by the rest of the blog, as it’s a topic I like to talk about repeatedly.

Using Arduino Simulator

Using Arduino Simulator is very simple:

  • Clone the solution’s repo
  • In the Sketch file, put the code for your machine
  • Create one or more Schedules
  • Run the simulation

You can create the Schedule in the main() function of the main.cpp file, although it is normal, if your IDE allows it, to create several Tests, each with its own actions.

To run the simulation, you have the functions,

  • simulate_seconds()
  • simulate_milliseconds()
  • simulate_microseconds()

By default, the simulation is performed every microsecond. In many cases, this step is unnecessarily small. For example, if your code ‘does things’ every second, simulating it every microsecond is a waste of time.

Therefore, you can adjust the simulation step with the SIMULATION_STEP_US variable. For example, you can set it to 100 to have the simulation run every 100us.

You can access the variables of the simulated hardware with the collections

  • Gpio_Mode[] and Gpio_Status
  • ADC_Input[]
  • PWM_Output[]
  • Interrupt[]

You can also simulate the occurrence of an interruption with the SIMULATE_INTERRUPT(numInterrupt, status) function. It will execute the code you have defined in your Sketch with atachInterrupt(...).

Finally, you can also save machine states with the SAVE_STATE function, which creates a ‘snapshot’ with the machine’s state at a given moment.

Arduino Simulator is Open Source, and all the code and documentation are available in the repo https://github.com/luisllamasbinaburo/Arduino_Simulator