TDD - You can write code without actual hardware (Part III)

TDD - You can write code without actual hardware (Part III)

This post is an extension of "TDD - You can write code without actual hardware (Part II)". Previously, eeprom_controller.c includes the read-byte function and write-byte function. Let's start to add a multiple write-byte test and a multiple read-byte test in test_eeprom_controller.c.

//test_eeprom_controller.c
void test_eeprom_controller_read_buffer(void)
{
    uint16_t i;
    uint8_t r_buffer[EEPROM_SIZE];

    TEST_ASSERT_EQUAL(true, eeprom_read_buffer(0, &r_buffer[0], EEPROM_SIZE));
    for(i = 0; i < EEPROM_SIZE; i++)
    {
        TEST_ASSERT_EQUAL(0xFF, r_buffer[i]);
    }
}

After setup, EEPROM data is all set to 0xFF. eeprom_read_buffer function's first parameter is the start address and the second parameter is a pointer of read data and the third parameter is the length of read data. Let's write production code to pass this test.

//eeprom_controller.h
bool eeprom_read_buffer(uint32_t address, uint8_t* r_data, uint16_t length);
//eeprom_controller.c
bool eeprom_read_buffer(uint32_t address, uint8_t* r_data, uint16_t length)
{
    bool op_status = false;

    flash_read(address, r_data, length);
    op_status = true;

    return op_status;
}

To avoid the array's out-of-boundary issue, a parameter check needs to be added. A first test case is when the address is greater than EEPROM_SIZE assuming the EEPROM address starts at 0. The second test case is when r_data is NULL(0). Lastly, address + length is out-of-boundary.

//test_eeprom_controller.c
void test_eeprom_controller_read_buffer(void)
{
    uint16_t i;
    uint8_t r_buffer[EEPROM_SIZE];

    //input range sanity test
    TEST_ASSERT_EQUAL(false, eeprom_read_buffer(EEPROM_SIZE, &r_buffer[0], EEPROM_SIZE));
    TEST_ASSERT_EQUAL(false, eeprom_read_buffer(1, &r_buffer[0], EEPROM_SIZE));
    TEST_ASSERT_EQUAL(false, eeprom_read_buffer(0, &r_buffer[0], EEPROM_SIZE+1));
    TEST_ASSERT_EQUAL(false, eeprom_read_buffer(0, NULL, EEPROM_SIZE));

    TEST_ASSERT_EQUAL(true, eeprom_read_buffer(0, &r_buffer[0], EEPROM_SIZE));
    for(i = 0; i < EEPROM_SIZE; i++)
    {
        TEST_ASSERT_EQUAL(0xFF, r_buffer[i]);
    }
}

This test will fail because the current production code only returns true. Let's modify the production code to pass the test.

//eeprom_controller.c
bool eeprom_read_buffer(uint32_t address, uint8_t* r_data, uint16_t length)
{
    bool op_status = false;
    if(r_data != 0 && (address < EEPROM_SIZE) && ((address + length - 1) < EEPROM_SIZE))
    {
        flash_read(address, r_data, length);
        op_status = true;
    }

    return op_status;
}

One last function to add in eeprom_controller.c: eeprom_write_buffer. Let's add a test case first. The address from 10 to 12 will be written as 0s. Then read back to verify.

//test_eeprom_controller.c
void test_eeprom_controller_write_buffer(void)
{
    uint32_t test_address_start;
    uint32_t test_length;
    uint16_t i;
    uint8_t test_value[5] = {0, 0 ,0, 0, 0};
    uint8_t read_value[5] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

    test_address_start = 10;
    test_length = 3;
    eeprom_write_buffer(test_address_start, &test_value[0], test_length);
    eeprom_read_buffer(test_address_start, &read_value[0], test_length);
    for(i = 0 ; i < test_length; i++)
    {
        TEST_ASSERT_EQUAL(0, read_value[i]);
    }

The production code needs to be written.

//eeprom_controller.h
bool eeprom_write_buffer(uint32_t address, const uint8_t* w_data, uint16_t length);
//eeprom_controller.c
bool eeprom_write_buffer(uint32_t address, const uint8_t* w_data, uint16_t length)
{
    bool op_status = false;
    flash_write(address, w_data, length);
    op_status = true;
    return op_status;
}

This function also has three parameters. An input parameter check is required. A first test case is when the address is less than EEPROM_SIZE. The second test case is when w_data is NULL(0). The third test case is where address + length is within the valid array index range.

//test_eeprom_controller.c
void test_eeprom_controller_write_buffer(void)
{
    uint32_t test_address_start;
    uint32_t test_length;
    uint16_t i;
    uint8_t test_value[5] = {0, 0 ,0, 0, 0};
    uint8_t read_value[5] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

    //input range sanity test
    TEST_ASSERT_EQUAL(false, eeprom_write_buffer(EEPROM_SIZE, &test_value[0], EEPROM_SIZE));
    TEST_ASSERT_EQUAL(false, eeprom_write_buffer(1, &test_value[0], EEPROM_SIZE));
    TEST_ASSERT_EQUAL(false, eeprom_write_buffer(0, &test_value[0], EEPROM_SIZE+1));
    TEST_ASSERT_EQUAL(false, eeprom_write_buffer(0, NULL, EEPROM_SIZE));

    test_address_start = 10;
    test_length = 3;
    eeprom_write_buffer(test_address_start, &test_value[0], test_length);
    eeprom_read_buffer(test_address_start, &read_value[0], test_length);
    for(i = 0 ; i < test_length; i++)
    {
        TEST_ASSERT_EQUAL(0, read_value[i]);
    }
}

The test will be failed because the current implementation only returns true. Let's add an input check. This check will be the same as eeprom_read_buffer.

bool eeprom_write_buffer(uint32_t address, const uint8_t* w_data, uint16_t length)
{
    bool op_status = false;

    if(w_data != 0 && (address < EEPROM_SIZE) && ((address + length - 1) < EEPROM_SIZE))
    {
        flash_write(address, w_data, length);
        op_status = true;
    }

    return op_status;
}

Finally, all tests are passed and also production code is ready to go! This is the final post on developing eeprom_controller.c module. With the TDD process, it can write a good quality production code and prove the functionality by unit testing.

Did you find this article valuable?

Support Hyunwoo Choi by becoming a sponsor. Any amount is appreciated!