Test embedded software with Google Test

Introduction

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.

Test tool

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.

Test creation

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`.

Makefile creation

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) 

Test run

When I run make, it looks like this: test.png I passed. (Because it is made to pass)

in conclusion

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

Test embedded software with Google Test
Authenticate Google with Django
Primality test with Python
Strengthen with code test ⑦
Strengthen with code test ③
Strengthen with code test ⑤
Strengthen with code test ④
Primality test with python
Strengthen with code test ②
Strengthen with code test ①
Strengthen with code test ⑧
Strengthen with code test ⑨
Try Google Mock with C
Unit test flask with pytest
Test standard output with Pytest
Study Python with Google Colaboratory
About learning with google colab
Do embedded programming by test-driven development with google test-SOLID design-
Load test Websocket with Locust
Mount google drive with google-drive-ocamlfuse
Access Google Drive with Python
Try OpenCV with Google Colaboratory
Translate PHP_UML --help with google translate
Deep Embedded Clustering with Chainer 2.0
Test Driven Development with Django Part 3
Google Test / Mock personal cheat sheet
Test Driven Development with Django Part 4
Test Driven Development with Django Part 6
Test Driven Development with Django Part 2
Google App Engine development with Docker
Unit test log output with python
OpenCV feature detection with Google Colaboratory
Test Driven Development with Django Part 1
Play with Turtle on Google Colab
Introducing Google Map API with rails
Test Driven Development with Django Part 5
Controlling test reruns with Luigi + pytest