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.
Subscribe to my newsletter
Read articles from Hyunwoo Choi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by