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 Children
No Data
Related