Until now, I haven't done TDD mainly for embedded development, but I read Embedded programming by test-driven development and wanted to introduce it. Has increased. I am studying while doing Development of STM32 Nucleo Board, so I hope to apply TDD to this.
Unity is introduced in the book, but I will use Google Test. Introduced Google Test / Google Mock Can it be done in 1 minute? No installation required, Google Test sample for Linux I referred to. Google Test has downloaded 1.8.0 from here.
In the case of TDD, the test does not need to be created first, but this time I will test the code that I have already created. The test target is the USART driver created by STM32 Nucleo Board bare metal hello world. The USART driver has been modified to make it easier to test. (The source code is here) Since there are waiting for reception and transmission, it is difficult to implement with only normal test cases. Therefore, use Mock to implement a substitute for HW-dependent IO implementation. Please refer to the Google Test Manual as the detailed grammar cannot be explained.
test_usart.cpp
//Test case description file
#include "gtest/gtest.h"
#include "gmock/gmock.h"
//I want to be able to call the function under test
// extern "C"Not interpreted as C without
extern "C" {
#include "usart_driver.h"
#include "stm32f303x8.h"
}
using ::testing::_;
using ::testing::Invoke;
class MockIo{
public:
MOCK_METHOD2(SetBit, void (__IO void*, uint32_t ));
MOCK_METHOD2(ClearBit, void (__IO void*, uint32_t ));
MOCK_METHOD2(ReadBit, uint32_t (__IO void*, uint32_t ));
MOCK_METHOD1(ClearReg, void (__IO void* ));
MOCK_METHOD2(WriteReg, void (__IO void*, uint32_t ));
MOCK_METHOD1(ReadReg, uint32_t (__IO void* ));
void FakeSetBit(__IO void* address, uint32_t bit){
*((uint32_t*)address) |= bit;
}
void FakeClearBit(__IO void* address, uint32_t bit){
*((uint32_t*)address) &= ~bit;
}
void FakeClearReg(__IO void* address){
*((uint32_t*)address) = 0;
}
void FakeWriteReg(__IO void* address, uint32_t data){
*((uint32_t*)address) = data;
}
void DelegateToVirtual() {
ON_CALL(*this, SetBit(_, _)).WillByDefault(Invoke(this, &MockIo::FakeSetBit));
ON_CALL(*this, ClearBit(_, _)).WillByDefault(Invoke(this, &MockIo::FakeClearBit));
ON_CALL(*this, ClearReg(_)).WillByDefault(Invoke(this, &MockIo::FakeClearReg));
ON_CALL(*this, WriteReg(_, _)).WillByDefault(Invoke(this, &MockIo::FakeWriteReg));
}
};
MockIo *mock;
extern "C" {
void SetBit(__IO void* address, uint32_t data){
mock->SetBit(address, data);
}
void ClearBit(__IO void* address, uint32_t data){
mock->ClearBit(address, data);
}
void ReadBit(__IO void* address, uint32_t data){
mock->ReadBit(address, data);
}
void ClearReg(__IO void* address){
mock->ClearReg(address);
}
void WriteReg(__IO void* address, uint32_t data){
mock->WriteReg(address, data);
}
uint32_t ReadReg(__IO void* address){
return mock->ReadReg(address);
}
}
The MockIo class replaces the register configuration interface io_reg.h used by the USART driver.
This is used when running the test, so it is possible to determine whether the register settings and sequences are correct.
The DelegateToVirtual () function describes the process you want to execute when you call Mock.
For example
ON_CALL(*this, SetBit(_, _)).WillByDefault(Invoke(this,&MockIo::FakeSetBit));
If you write, FakeSetBit will be called at the same time as SetBit is called.
This is done because the pseudo register settings cannot be retained only with Mock.
Next, write a test case.
test_usart.Continuation of cpp
RCC_TypeDef *virtualRcc;
GPIO_TypeDef *virtualGpio;
USART_TypeDef *virtualUsart;
//UsartTest is like a group name that groups test cases together
class UsartTest : public ::testing::Test {
protected:
//Grouped test cases before running each test case
//Call this function. Test code is neat if you put in common initialization processing
virtual void SetUp()
{
mock = new MockIo();
//The test environment creates a pseudo register area because the registers are not mapped to memory
virtualRcc = new RCC_TypeDef();
virtualGpio = new GPIO_TypeDef();
virtualUsart = new USART_TypeDef();
UsartCreate(virtualRcc, virtualGpio, virtualUsart);
}
//A function called after executing a test case like SetUp. Describe the common cleanup.
virtual void TearDown()
{
delete mock;
delete virtualRcc;
delete virtualGpio;
delete VirtualUsart;
}
};
//test case
TEST_F(UsartTest, Init)
{
mock->DelegateToVirtual();
EXPECT_CALL(*mock, SetBit(_, _)).Times(6); //The number of times does not matter, so it is subtle, but because there is a Warning
EXPECT_CALL(*mock, ClearReg(_)).Times(3);
EXPECT_CALL(*mock, WriteReg(_, _)).Times(1);
UsartInit();
EXPECT_EQ(RCC_AHBENR_GPIOAEN, virtualRcc->AHBENR & RCC_AHBENR_GPIOAEN);
EXPECT_EQ(GPIO_MODER_MODER2_1|GPIO_MODER_MODER15_1, virtualGpio->MODER);
EXPECT_EQ(0x700, virtualGpio->AFR[0]);
EXPECT_EQ(0x70000000, virtualGpio->AFR[1]);
EXPECT_EQ(RCC_APB1ENR_USART2EN, virtualRcc->APB1ENR);
EXPECT_EQ(8000000L/115200L, virtualUsart->BRR);
EXPECT_EQ(USART_CR1_RE|USART_CR1_TE|USART_CR1_UE, virtualUsart->CR1);
}
using ::testing::Return;
TEST_F(UsartTest, IsReadEnable)
{
EXPECT_CALL(*mock, ReadBit(&virtualUsart->ISR, USART_ISR_RXNE)).WillOnce(Return(0));
EXPECT_EQ(0, UsartIsReadEnable());
EXPECT_CALL(*mock, ReadBit(&virtualUsart->ISR, USART_ISR_RXNE)).WillOnce(Return(USART_ISR_RXNE));
EXPECT_EQ(USART_ISR_RXNE, UsartIsReadEnable());
}
TEST_F(UsartTest, IsWriteEnable)
{
EXPECT_CALL(*mock, ReadBit(&virtualUsart->ISR, USART_ISR_TXE)).WillOnce(Return(0));
EXPECT_EQ(0, UsartIsWriteEnable());
EXPECT_CALL(*mock, ReadBit(&virtualUsart->ISR, USART_ISR_TXE)).WillOnce(Return(USART_ISR_TXE));
EXPECT_EQ(USART_ISR_TXE, UsartIsWriteEnable());
}
TEST_F(UsartTest, Read)
{
EXPECT_CALL(*mock, ReadBit(&virtualUsart->ISR, USART_ISR_RXNE)).WillRepeatedly(Return(0));
EXPECT_CALL(*mock, ReadBit(&virtualUsart->ISR, USART_ISR_RXNE)).WillRepeatedly(Return(USART_ISR_RXNE));
EXPECT_CALL(*mock, ReadReg(&virtualUsart->RDR)).WillRepeatedly(Return('a'));
EXPECT_EQ('a', UsartRead());
}
TEST_F(UsartTest, Write)
{
mock->DelegateToVirtual();
char c = 's';
EXPECT_CALL(*mock, ReadBit(&virtualUsart->ISR, USART_ISR_TXE)).WillRepeatedly(Return(0));
EXPECT_CALL(*mock, ReadBit(&virtualUsart->ISR, USART_ISR_TXE)).WillRepeatedly(Return(USART_ISR_TXE));
EXPECT_CALL(*mock, WriteReg(&virtualUsart->TDR, c));
UsartWrite(c);
EXPECT_EQ(c, virtualUsart->TDR);
}
For the time being, I am writing a test for each interface of usart_driver.h. The part where HW sets the register such as reception completion is realized by setting the return value with ʻEXPECT_CALL`.
The build will be in the host environment instead of the target environment.
Place Google Test in the location of GTEST_DIR.
First run make gtest -gen
and run the test with make
.
# Makefile
# gtest_main.cc is the main function provided by Google Test,
# gmock-gtest-all.cc is a file containing all Google Test
# -Also note that lpthread is attached.
#After make or make all, build and execute.
TESTNAME = test_usart
GTEST_DIR = ../../../../../..
TEST_DIR = .
CODE_DIR = ..
INC_DIR = ../../..
INCLUDE = -I$(GTEST_DIR) -I$(INC_DIR)/include
SRCS = $(CODE_DIR)/usart_driver.c
OBJECTS = usart_driver.o
all: $(OBJECTS) $(TESTNAME)
./$(TESTNAME)
$(TESTNAME): $(OBJECTS)
g++ -o $(TESTNAME) test_usart.cpp $(GTEST_DIR)/googletest/googletest/src/gtest_main.cc $(GTEST_DIR)/gmock-gtest-all.cc $(INCLUDE) -lpthread $(OBJECTS) -D DEBUG_GTEST
$(OBJECTS): $(SRCS)
gcc -c $(SRCS) $(INCLUDE) -DEBUG_GTEST
clean:
rm *.o $(TESTNAME)
gtest-gen:
python $(GTEST_DIR)/googletest/googlemock/scripts/fuse_gmock_files.py $(GTEST_DIR)
When I run make, it looks like this: I passed. (Because it is made to pass)
I'm not sure if this is enough for test cases due to lack of experience. However, now that I can write tests, I would like to actually incorporate TDD and gain experience. I think that there are many things that cannot be confirmed without the actual machine, but I hope that the design quality will improve by considering the test.
Recommended Posts