Unit testing using ncs and Unity/CMock

Hi Nordic Devzone,

I started developing a product around nrf52840 and nrf Connect SDK. It was my first time using Zephyr and the SDK, so after a few weeks of getting into it, I started to write some firmware, and I would now like to implement unit tests. I started following the instructions on this page : https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/test_and_optimize/testing_unity_cmock.html

I tried the example_test fine, but I am a bit stuck on how best to do it on my files. I started on some of my simplest files, ui.h/ui.c :

ui.h:

#ifndef UI_H_
#define UI_H_

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/device.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/logging/log.h>

#define STEP_SIZE PWM_USEC(2000)
#define UI_COLOR_MAX 10000
#define UI_LED_INTENSITY_LOW 200

typedef struct
{
    uint16_t red;   /* Intensity 0-UI_COLOR_MAX */
    uint16_t green; /* Intensity 0-UI_COLOR_MAX */
    uint16_t blue;  /* Intensity 0-UI_COLOR_MAX */
} ui_rgb_t;

uint8_t ui_init();
uint8_t ui_set_rgb_on(ui_rgb_t *color);
uint8_t ui_set_rgb_off();

#endif /* UI_H_ */

ui.c :

#include <include/ui.h>

LOG_MODULE_REGISTER(UI, LOG_LEVEL_DBG);

static const struct pwm_dt_spec red_pwm_led =
    PWM_DT_SPEC_GET(DT_ALIAS(pwmled1red));
static const struct pwm_dt_spec green_pwm_led =
    PWM_DT_SPEC_GET(DT_ALIAS(pwmled0green));
static const struct pwm_dt_spec blue_pwm_led =
    PWM_DT_SPEC_GET(DT_ALIAS(pwmled2blue));

ui_rgb_t off = {0, 0, 0};

uint8_t ui_set_rgb_on(ui_rgb_t *color)
{
    int ret;
    uint32_t r, g, b;
    if (color->red >= UI_COLOR_MAX)
    {
        color->red = UI_COLOR_MAX;
    }
    if (color->green >= UI_COLOR_MAX)
    {
        color->green = UI_COLOR_MAX;
    }
    if (color->blue >= UI_COLOR_MAX)
    {
        color->blue = UI_COLOR_MAX;
    }

    r = red_pwm_led.period / UI_COLOR_MAX * color->red;
    ret = pwm_set_pulse_dt(&red_pwm_led, r);
    if (ret != 0)
    {
        LOG_ERR("Error %d: red write failed.", ret);
        LOG_HEXDUMP_ERR(color, sizeof(ui_rgb_t), "Error while writing color :");
        return 0;
    }

    g = green_pwm_led.period / UI_COLOR_MAX * color->green;
    ret = pwm_set_pulse_dt(&green_pwm_led, g);
    if (ret != 0)
    {
        LOG_ERR("Error %d: green write failed.", ret);
        LOG_HEXDUMP_ERR(color, sizeof(ui_rgb_t), "Error while writing color :");
        return 0;
    }

    b = blue_pwm_led.period / UI_COLOR_MAX * color->blue;
    ret = pwm_set_pulse_dt(&blue_pwm_led, b);
    if (ret != 0)
    {
        LOG_ERR("Error %d: blue write failed.", ret);
        LOG_HEXDUMP_ERR(color, sizeof(ui_rgb_t), "Error while writing color :");
        return 0;
    }

    return 1;
}

uint8_t ui_set_rgb_off()
{
    ui_rgb_t off;
    off.red = 0;
    off.green = 0;
    off.blue = 0;
    return ui_set_rgb_on(&off);
}

uint8_t ui_init()
{
    if (!device_is_ready(red_pwm_led.dev) ||
        !device_is_ready(green_pwm_led.dev) ||
        !device_is_ready(blue_pwm_led.dev))
    {
        LOG_ERR("Error: one or more PWM devices not ready");
        return 0;
    }
    return 1;
}

I wanted first to test the ui_init function, so I started writing this ui_test.c file based on example_test.c :

/*
 * Copyright (c) 2019 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */
#include <unity.h>
#include <ui.h>
#include <stdbool.h>
#include "device/cmock_device.h"
#include "pwm/cmock_pwm.h"

static const struct pwm_dt_spec red_pwm_led =
    PWM_DT_SPEC_GET(DT_ALIAS(pwmled1red));
static const struct pwm_dt_spec green_pwm_led =
    PWM_DT_SPEC_GET(DT_ALIAS(pwmled0green));
static const struct pwm_dt_spec blue_pwm_led =
    PWM_DT_SPEC_GET(DT_ALIAS(pwmled2blue));

void setUp(void)
{
}

void test_ui_init(void)
{
	int err;

	__cmock_device_is_ready_ExpectAndReturn(red_pwm_led.dev, 0);
	__cmock_device_is_ready_ExpectAndReturn(green_pwm_led.dev, 0);
	__cmock_device_is_ready_ExpectAndReturn(blue_pwm_led.dev, 0);
	//__cmock_foo_execute_ExpectAndReturn(0);

	err = ui_init(NULL);
	TEST_ASSERT_EQUAL(1, err);
}

/* It is required to be added to each test. That is because unity's
 * main may return nonzero, while zephyr's main currently must
 * return 0 in all cases (other values are reserved).
 */
extern int unity_main(void);

int main(void)
{
	(void)unity_main();

	return 0;
}

I mocked the device_is_ready function by adding 

cmock_handle($ENV{ZEPHYR_BASE}/include/zephyr/device.h device)
to the CMakeLists.txt file, and then I wanted to use 
__cmock_device_is_ready_ExpectAndReturn
function. I wondered what to use as the "device" argument, and figured I'd declare the leds the same way I did when using my board. However, obviously, the native_posix board does not have such leds so it doesn't work.

Is my general approach correct ? Do you have any tips on testing such functions that use hardware-related functions ?

Thanks in advance.

Nicolas Goualard

Parents Reply
  • Hi Sigurd,

    Thanks for your answer. I tried that this morning, adding a native_posix.overlay file with aliases matching my custom board:

    // Adding fake leds to the native_posix board to be able to test the init function.
    
    pwmleds {
        compatible = "pwm-leds";
        pwm_led0_green: pwm_led_0 {
            pwms = <&pwm0 0 PWM_MSEC(20) PWM_POLARITY_INVERTED>;
            label = "Green PWM LED 0";
        };
        pwm_led1_red: pwm_led_1 {
            pwms = <&pwm0 1 PWM_MSEC(20) PWM_POLARITY_INVERTED>;
            label = "Red PWM LED 1";
        };
        pwm_led2_blue: pwm_led_2 {
            pwms = <&pwm0 2 PWM_MSEC(20) PWM_POLARITY_INVERTED>;
            label = "Blue PWM LED 1";
        };
    };
    
    aliases {
        pwmled0green = &pwm_led0_green;
        pwmled1red = &pwm_led1_red;
        pwmled2blue = &pwm_led2_blue;
    };

    I couldn't get it to build properly, but after a bit more research, it seems the pwm peripheral is not implemented on the native_posix board : https://docs.zephyrproject.org/latest/boards/posix/native_posix/doc/index.html#peripherals

    That same page also states that the native_posix architecture "can only be used to develop and test application code which is far decoupled from the HW", whereas my current use-case is intimately coupled with hardware, so that might be the wrong way to go.

    Do you think that using the nrf52_bsim board would be more suitable ? It's not entirely clear to me if the pwm peripheral is provided on this board. https://docs.zephyrproject.org/latest/boards/posix/nrf52_bsim/doc/index.html#nrf52-bsim

Children
Related