Showing posts with label TDD. Show all posts
Showing posts with label TDD. Show all posts

TDD-ing a Circular Buffer in Google Test Framework

 TDD is quite a popular practice among the software developers but in the firmware development case it's not quite so. One of the reasons behind this is the hurdle in setting up the TDD environment. In this blog post I am not going to explain the importance of doing TDD, rather in this post I will be showing you how we can set up a TDD build environment using google test and  develop a circular buffer following the TDD approach. So Let’s engage in TDD-ing 


gTest Setup 


Firstly we have to install google Test , this is available from the apt-repository 

~$ sudo apt-get install libgtest-dev


The above command will pull down the sources and it is upto us to build the gTest. This process Requires CMake

~$ sudo apt-get install cmake
~$ sudo apt-get install build-essential


Now go to gtest src which got downloaded by apt & build gTest

/usr/src/gtest$ sudo cmake CMakeLists.txt
/usr/src/gtest$ sudo make


Now Lets copy the libraries to system default /usr/lib so that it is accessible from any PATH  

/usr/src/gtest$ sudo cp lib/*.a /usr/lib


Requirements Gathering  


Before starting to code we do have a mental model of the module that we want to develop . The idea of TDD is to write test cases first and then do coding . We will do the same but first let’s write down the requirements of the module in our imagination. 


Circular Buffer spec 

  1. The buffer will be 4 in size 

  2. Data can be put into the buffer 

  3. Data can be read from the buffer

  4. The buffer should be FIFO in nature 

  5. The buffer should be fixed sized 

  6. User should know if buffer is empty

  7. User should have the option to clear the buffer


TDD project Folder Structure 


The Project folder has the following structure 



Now let’s look what each of the elements are 


CMakeLists.txt

This is the CMake file required for the overall build . 


It has linkage to the gTest binaries and rules to add the module headers under include folder 

CBuffer.h

This is the Module Header File

CBuffer.cpp

This is the Module Implementation File 

main.cpp

The Test Driver and the Test framework

It contains all the Test Cases  


Let’s create all the folders and files 


~/$ mkdir circular-buffer
~/$ cd circular-buffer/
~/circular-buffer$ mkdir include src
~/circular-buffer$ touch include/CBuffer.h
~/circular-buffer$ touch src/CBuffer.cpp
~/circular-buffer$ touch src/main.cpp

~/circular-buffer$ touch CMakeLists.txt 


Linking gTest & CMake 


Now we will prepare the CMake makefile so that our sources gets linked with the compiled gTest libraries that we built at top . This CMake file is very easy to understand . the contents are 


CMakeLists.txt 

cmake_minimum_required(VERSION 2.8.9)
project (circular-buffer-test)

#For the static library:
set ( PROJECT_LINK_LIBS libgtest.a libgtest_main.a )
link_directories( /usr/lib )

set(CMAKE_CXX_FLAGS "-Wall -std=c++11 -pthread")

#Bring the headers
include_directories(include)

#if multiple files
file(GLOB SOURCES "src/*.cpp")
add_executable(circular-buffer-test ${SOURCES})

target_link_libraries(circular-buffer-test ${PROJECT_LINK_LIBS} )

message("CMAKE_CXX_FLAGS is ${CMAKE_CXX_FLAGS}")
message("CMAKE_CXX_FLAGS_DEBUG is ${CMAKE_CXX_FLAGS_DEBUG}")
message("CMAKE_CXX_FLAGS_RELEASE is ${CMAKE_CXX_FLAGS_RELEASE}")


Test Driver Structure 


Now that our CMakeFile is ready we will prepare the main.cpp the Test Driver File .


main.cpp 

#include <iostream>      //standard header inclusion
#include <gtest/gtest.h> //google test header inclusion

#include "CBuffer.h"     //module header inclusion

int main(int argc, char** argv) {
  // The following line must be executed to initialize Google Mock
  // (and Google Test) before running the tests.
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}


Now let’s put some minimum code in our sources and check if build passes


CBuffer.h

#ifndef CBUFFER_H_   /* Include guard */
#define CBUFFER_H_


#endif


CBuffer.cpp

#include "CBuffer.h"



Build System Check 

Now let’s check if the Test Build system prepared so far works or not

~/circular-buffer$ cmake .


The job of CMake is to create a makefile


Let’s use make to build our executable binary 

~/circular-buffer$ make clean
~/circular-buffer$ make


Now run the executable created

~/circular-buffer$ ./circular-buffer-test
[==========] Running 0 tests from 0 test suites.
[==========] 0 tests from 0 test suites ran. (0 ms total)
[  PASSED  ] 0 tests.


Writing The First Test Case


In TDD we have to follow 4 major steps 

  1. Setup 

  2. Exercise

  3. Verify

  4. Cleanup 


Each Test Case should go through the above steps 


  • We will write test cases for spec 6 & 7 

  • We will call clear function to empty out the buffer 

  • And then check if buffer is empty 


Now let’s compile again 

$ make clean && make


You will get compilation error 


Getting compilation/build errors first is also part of The TDD process. The mandatory rule is to write test cases first no matter what . The hidden gem of TDD is that by writing the test cases first we get to think about the module API calls and the code grows with this approach 


Now Let’s put code

CBuffer.cpp

CBuffer.h 


Next step is to add our 3rd step Verify

main.cpp

 


Now Compile again and you will see successful build output 

$ make clean && make 


Let’s run the executable binary 

~/circular-buffer$ ./circular-buffer-test


This time we see Test Case output of the verification stage. In this step we see Test cases getting failed This is the way TDD development progresses. We observe code getting failed and then we write code to pass the test cases. This is what we are going to do now 


CBuffer.cpp


Add the orange marked code and Now if you compile and run the test executable again then you will see that the test have passed 


Similarly you will be adding all the other test cases and code will get developed along the away 

I am not going to explain all the other test cases here anymore as this has already been a very long post. You can find the rest of the test cases along with all the other source files available in this link 

https://github.com/hassin23ayz/TDD-practice/tree/master/cpp/circular-buffer 


TDD & Arduino

TDD & Arduino

gTest & gMock

sublime


TDD is quite a popular practice among software developers but in the firmware development case, it's not quite so. One of the reasons behind this is the environment setup hurdle. In this blog post, I am not going to explain the importance of doing TDD, rather in this post, I will be showing you how we can set up a TDD build environment using google test, google mock & Arduino mock. 


gTest Setup 

Firstly we have to install google Test

~$ sudo apt-get install libgtest-dev

Now we have to build gTest , but for that, you need to have cmake tool installed 

~$ sudo apt-get install cmake
~$ sudo apt-get install build-essential

Now go to gtest src which got downloaded by apt & build gTest

/usr/src/gtest$ sudo cmake CMakeLists.txt
/usr/src/gtest$ sudo make

Now let's copy the libraries to system default /usr/lib 

/usr/src/gtest$ sudo cp lib/*.a /usr/lib


Now that you have installed gTest you can do some basic TDD-ing using gTest .

  • Basically, you have to write some test cases first, 

  • then you run the code you want to Test using the Test Suite [e.g. gTest suite] 

  • You see the Tests to be failed 

  • You refactor the code and redo the process again 


I will definitely recommend you to read James Greening’s Test Driven-Development- for-Embedded-C book for understanding the TDD concepts


Also when I started TDD I followed this tutorial series from codesBay at youtube Google C++ Testing GTest GMock Framework - YouTube 

Here you will find an excellent hands-on intro of using gTest to test drive your code. 


gMock Setup


Mocking is very important in TDD. For example, the Serial class in Arduino has hardware implementations, so if you want to test a function/code portion, which uses this class then you can’t do that in your host environment. But what if the Test Suite running at your host mocks the Serial Class API in a way so that when your code calls a function of the Serial Class e.g. println() then the mocked function gets called rather than the actual one, then this way you can test drive your Arduino code at the host. 


Google Mock can mock any class and help you Test Drive the particular portion of the code you want to test. So we will setup google mock 


Setup of gMock is similar as gTest 

 

~$ sudo apt-get install google-mock
~$ sudo apt-get install cmake
/usr/src/gmock$ sudo cmake CMakeLists.txt
/usr/src/gmock$ sudo make
/usr/src/gmock$ sudo cp *.a /usr/lib


After the gTest and gMock setup I had the following libraries in my /usr/lib directory 



Now I would suggest you finish the video playlist by codesBay as it will also cover the concept of mocking 


Arduino-Mock Setup 


Our motive is to seamlessly TDD-drive our Arduino Code. We want to switch back and forth between the host TDD environment and the Arduino IDE. 


[note: I do not use Arduino IDE as the main editor, i use sublime Text Editor, I Only use Arduino IDE for compiling the code for the Target Arduino Boards ] 


We will progress towards a sample project where this seamless setup will be achieved, but before that, we have to make sure that we can mock Arduino Libraries. Obviously, we can collect Each Arduino Class header file and use google Mock to replace each of them but what if someone else has done this tedious step, fortunately, yes https://github.com/mitchyboy9/arduino-mock/tree/master/src 


Clone the project 

~$ git clone https://github.com/ikeyasu/arduino-mock.git

Run the default build script 

~/arduino-mock$ ./build.sh


This script will download all necessary sources, create a build directory &  build Arduino mock library, at the end, it will run some tests too. so after the successful build, you can find the Arduino mock library here 


~/arduino-mock$ ls build/dist/lib


Libarduino_mock.a


Remember when we built gTest and gMock we copied the libraries to /usr/lib , we will do the same here  

~/arduino-mock$ sudo cp build/dist/lib/libarduino_mock.a /usr/lib


So now here are the libraries under /usr/lib

~/arduino-mock$ ls -l /usr/lib/*.a


-rw-r--r-- 1 root root 3328570 Sep  4 13:57 /usr/lib/libarduino_mock.a
-rw-r--r-- 1 root root 2519988 Jun 12 12:47 /usr/lib/libgmock.a
-rw-r--r-- 1 root root 2524264 Jun 12 12:47 /usr/lib/libgmock_main.a
-rw-r--r-- 1 root root 1811188 Jun 12 12:44 /usr/lib/libgtest.a
-rw-r--r-- 1 root root    3964 Jun 12 12:44 /usr/lib/libgtest_main.a


Arduino-TDD Sample Project 


Now the time has come to create a sample project where we will  TDD-drive a simple Arduino sketch 


Create a folder 

~/projects/TDD-practice$ mkdir arduino-mock-sample


Create src , include & build directories 

Now I would suggest you finish the video playlist as it will also cover the concept of mocking 

~/projects/TDD-practice/arduino-mock-sample$ mkdir src
~/projects/TDD-practice/arduino-mock-sample$ mkdir include
~/projects/TDD-practice/arduino-mock-sample$ mkdir build 


Create the sketch file “arduino-mock-sample.ino” it is named the same name as the project folder so that you can open it by Arduino IDE 

~/projects/TDD-practice/arduino-mock-sample$ subl arduino-mock-sample.ino 


#include <Arduino.h>
// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin 13 as an output.
  pinMode(13, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}


Copy the Arduino Mock header files from the local git repo copy to the include directory under the project folder 

~/projects/TDD-practice/arduino-mock-sample$ cp -r ~/arduino-mock/include/arduino-mock include/


Create the CMakeLists.txt file under the project directory 

~/projects/TDD-practice/arduino-mock-sample$ subl CMakeLists.txt


Here are its contents 

cmake_minimum_required(VERSION 2.8.9)
project (arduino-mock-sample)

#For the static library:
set ( PROJECT_LINK_LIBS libgtest.a libgmock.a libgmock_main.a libgtest_main.a libarduino_mock.a )
link_directories( /usr/lib )

set(CMAKE_CXX_FLAGS "-Wall -std=c++11 -pthread")

#Bring the headers
include_directories(include)

#if multiple files
file(GLOB SOURCES "src/*.cpp")
add_executable(arduino-mock-sample-exe ${SOURCES})

target_link_libraries(arduino-mock-sample-exe ${PROJECT_LINK_LIBS} )

message("CMAKE_CXX_FLAGS is ${CMAKE_CXX_FLAGS}")
message("CMAKE_CXX_FLAGS_DEBUG is ${CMAKE_CXX_FLAGS_DEBUG}")
message("CMAKE_CXX_FLAGS_RELEASE is ${CMAKE_CXX_FLAGS_RELEASE}")


Now let’s create a test runner and write a very simple Test Case for the setup() function of the sketch 


~/projects/TDD-practice/arduino-mock-sample/src$ subl tests.cpp


#define UNIT_TESTING

#ifdef UNIT_TESTING

#include <iostream>
#include <gtest/gtest.h>
#include <gmock/gmock.h>  // Brings in Google Mock.
#include <arduino-mock/Arduino.h>

#include "../arduino-mock-sample.ino"

using namespace std;
using ::testing::AtLeast;
using ::testing::Return;
using ::testing::_;

TEST(sketch,setup) {
  ArduinoMock* arduinoMock = arduinoMockInstance();
  EXPECT_CALL(*arduinoMock, pinMode(13, OUTPUT));
  setup();
  releaseArduinoMock();
}

int main(int argc, char** argv) {
  // The following line must be executed to initialize Google Mock
  // (and Google Test) before running the tests.
  ::testing::InitGoogleMock(&argc, argv);
  return RUN_ALL_TESTS();
}

#endif


In this test runner, only one Test Case is put. Also on top, you can see the MACRO guard 


💡enable the “UNIT_TESTING” macro while doing TDD 

💡Comment out the “UNIT_TESTING” macro while compiling in Arduino IDE for the target Arduino board 


Create A script to automate the total process of cleaning, building, and running the tests at the host 


~/projects/TDD-practice/arduino-mock-sample/build$ subl clean-build-run.sh

The clean build script looks like 

#!/bin/bash
echo "cleaning the directory.."
find . ! -name 'clean-build-run.sh' -type f -exec rm -f {} +
echo "CMaking to generate makefile.."
cmake ..
echo "building.."
make
echo "running the executable.."
./arduino-mock-sample-exe


Finally, Let’s Run the Tests 

~/projects/TDD-practice/arduino-mock-sample/build$ ./clean-build-run.sh


If everything is okay you will see the only test passing, you can now comment out the “UNIT_TESTING” macro at tests.cpp file , open the sketch using Arduino IDE and perform compile, upload for your target Arduino board 


I am showing the tree of my arduino-mock-sample project folder so that you can better understand the files arrangement 

 

.
├── arduino-mock-sample.ino
├── build
│   ├── arduino-mock-sample-exe
│   ├── clean-build-run.sh
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   │   ├── 3.10.2
│   │   │   ├── CMakeCCompiler.cmake
│   │   │   ├── CMakeCXXCompiler.cmake
│   │   │   ├── CMakeDetermineCompilerABI_C.bin
│   │   │   ├── CMakeDetermineCompilerABI_CXX.bin
│   │   │   ├── CMakeSystem.cmake
│   │   │   ├── CompilerIdC
│   │   │   │   ├── a.out
│   │   │   │   ├── CMakeCCompilerId.c
│   │   │   │   └── tmp
│   │   │   └── CompilerIdCXX
│   │   │       ├── a.out
│   │   │       ├── CMakeCXXCompilerId.cpp
│   │   │       └── tmp
│   │   ├── arduino-mock-sample-exe.dir
│   │   │   ├── build.make
│   │   │   ├── cmake_clean.cmake
│   │   │   ├── CXX.includecache
│   │   │   ├── DependInfo.cmake
│   │   │   ├── depend.internal
│   │   │   ├── depend.make
│   │   │   ├── flags.make
│   │   │   ├── link.txt
│   │   │   ├── progress.make
│   │   │   └── src
│   │   │       └── tests.cpp.o
│   │   ├── cmake.check_cache
│   │   ├── CMakeDirectoryInformation.cmake
│   │   ├── CMakeOutput.log
│   │   ├── CMakeTmp
│   │   ├── comm-module-gprs-test-exe.dir
│   │   │   └── src
│   │   ├── feature_tests.bin
│   │   ├── feature_tests.c
│   │   ├── feature_tests.cxx
│   │   ├── Makefile2
│   │   ├── Makefile.cmake
│   │   ├── progress.marks
│   │   └── TargetDirectories.txt
│   ├── cmake_install.cmake
│   └── Makefile
├── CMakeLists.txt
├── include
│   └── arduino-mock
│       ├── Arduino.h
│       ├── EEPROM.h
│       ├── IRremote.h
│       ├── Serial.h
│       ├── serialHelper.h
│       ├── Spark.h
│       ├── SPI.h
│       ├── WiFi.h
│       └── Wire.h
└── src
    └── tests.cpp

15 directories, 46 files



Categories

Pages

Firmware Engineer

My photo
Works on Firmware, Embedded Linux, Smart Metering, RTOS, IoT backend

Contact Form

Name

Email *

Message *

Copyrighted by Hassin. Powered by Blogger.