I'm currently developing some low level drivers for an embedded platform in plain C. I use unity+cmock as a unit testing framework.
However while writing the low level stuff I often come across the following pattern:
Test:
void test_mcp2515_read_register(void)
{
spi_frame_t expected_frame = {{0}};
expected_frame.tx_length = 2;
expected_frame.rx_length = 3;
expected_frame.tx_data[0] = MCP2515_READ_CMD;
expected_frame.tx_data[1] = TEST_ADDR;
expected_frame.callback = callback_test;
spi_transmit_ExpectAndReturn(expected_frame, true);
mcp2515_read_register(TEST_ADDR, callback_test);
}
Implementation:
void mcp2515_read_register(uint8_t addr, spi_callback callback)
{
spi_frame_t frame = {{0}};
frame.tx_length = 2;
frame.rx_length = 3;
frame.tx_data[0] = MCP2515_READ_CMD;
frame.tx_data[1] = addr;
frame.callback = callback;
spi_transmit(frame);
}
As you can see there is a lot of duplication between the code between the test and the implementation.
Is this a problem? Am I writing my tests wrong? Or should I not bother writing tests at all for this low level stuff?
The efficiency of a test code doesn't usually matter. It depends on what you are trying to test, but duplicate code can indicate a design flaw.
In your case, you could perhaps have divided the mcp2515_read_register function in two parts: one which creates a struct and another which handles the SPI transmission.
The optimal OO program design would probably involve the following modules:
The SPI driver declares
spi_frame_t
as an opaque type, a struct which is only concerned with SPI data and communication. None outside the SPI driver knows or needs to know the contents of this struct. I don't know what the callback function does, but it doesn't look like something related to the SPI driver. It rather looks like something related to the code that calls the SPI driver.The CAN controller driver includes the SPI driver. It calls a "constructor" from the SPI driver to create a frame, then passes the frame on to the SPI communication routines. The CAN controller driver then has no tight coupling to SPI functionality. For example, it doesn't make sense to rewrite your CAN controller code because you found a bug in the SPI. Nor should you need a CAN controller to be able to use SPI, should you want to re-use the SPI driver in another project.
The test case either replaces the caller ("main") or the CAN controller code, depending on what part of the program you are trying to test. It uses the very same functions as the production code.