C++ unit testing & CI integration in GitHub (I/2)

I am working on a side project, Vulkan-Android, that is based on Java, JNI, C++, and Vulkan for Android platform. It also uses my C++ math library. Therefore, the requirement of my build and unit tests are around C++. First of all, I would make sure the unit tests in local are run properly.

C++ unit test on Mac OS

On Mac OS, I think the most convenient way to do unit testing for C++ is writing XCT in Xcode. In the beginning, we need to create a test plan in Xcode. It will helps us create a schema, then, in the test navigator, create a new Unit Test Target. Due to XCT was originally designed for Objective-C or Swift, if we wanna test our C++ code, we need a workaround by making the file extension name to be *.mm.

And then, write down the unit tests as below:


#import <XCTest/XCTest.h>
#include "Vector3d.h"
using namespace gfx_math;

@interface testVector3D : XCTestCase

@end

@implementation testVector3D

- (void)setUp {
    // Put setup code here. This method is called before the invocation of each test method in the class.
}

- (void)tearDown {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
}

- (void)testVectorBasic {
  Vector3Df a(1, 0, 0);
  
  float res = a.DotProduct(Vector3Df(1, 0, 0));
  XCTAssertEqual(res, 1.0f);
}

@end
Press Cmd + U, Xcode will execute the test plan for you.










C++ unit tests with CI

Technically, we should be able to find a CI tool to run these unit tests directly in their service. My first try was Circle-CI, but its Mac OS instance is not free. So, I moved to using GitHub Actions. GitHub has two options for C++ build, one is MS-Build, the other one is Make. I rather chose Make because it is available to run on my Mac OS machine, and if you want, you can integrate it with Google Test as well. As a beginner, I would tend to be not too aggressive, so I used C++ assert() to verify my functions.
 std::vector<Vector3Df> interPointList;
 result = RayIntersectWithSphere(ray, Vector3Df(-10, 0, 0), Vector3Df(0,0,0), 5.0f, interPointList);
 assert(result);
 assert(interPointList.size() == 2);
Then, create a Makefile like this. We can verify if this Makefile works or not in local first by inserting make clean && make build && ./unittest and submit it to the GitHub repo. In GitHub Actions, choosing create a new workflow with C/C++ with Make, and setup its config file as blow, it will be put under .github/workflows/
name: C/C++ CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: make clean
      run: make clean
    - name: make build
      run: make build
    - name: run unittest
      run: ./unittest
This config file will execute a few steps: clean, build, and run the executable file that is our unit tests.  After that, every time when committing a new patch to this repo (it could also run besides the master branch?), this workflow will be executed automatically. One last thing, don't forget to add its badge
into your README.md file. because it looks really cute.


We are also available to use Circle-CI Windows or Linux based Docker images. Although we need to install additional C++ compiler at the beginning, the detail setup and discussion please refer the discussion and the setting.

Android JNI build and testing with CI

For Android projects building in local, it is pretty easy, just run ./gradlew build. Following, in terms of Android C++/JNI testing, I think Google Test is the most convenient one to take. Thanks for AndroidGTestRunner [1] inspires me for doing this. Firstly, we need to add Google Test into our CMakeList build, so pointing to the googletest's CMakeList folder.

add_subdirectory(${THIRD_PARTY_DIR}/googletest gtest). 
target_link_libraries(gtest)


For the specific setting, please refer my unittest subfolder. Adding some simple gtest code as below. 
TEST(TestVector3D, basic) {
  Vector3Df a(1, 0, 0);

  float res = a.DotProduct(Vector3Df(1, 0, 0));
  ASSERT_EQ(res, 1.0f);
}
GTestRunner is responsible for talking to JNI and collect tests from gtest suite. It helps execute commands via CLI to runner that was built from googletest library.  

For launching an Android Emulator in Circle-CI, it is only available to be run on a Mac OS instance and with some complicated setup (I got an another idea by using GitHub Actions when I was writing this post, I will give another post then).

Finally, setup the configuration for Circle-CI workflow.

version: 2.1

orbs:
  android: circleci/android@0.2.1

jobs:
  build:
    executor: android/android

    steps:
      - checkout
      - run: git submodule update --init --recursive
      - run:
          name: Build unittests
          working_directory: ~/project/unittests
          command: ./gradlew build
As you can see, the workflow I did is checkout my repo, sync with submodules, and build my project.

That is all what I learned about C++ unit testing & CI integration in GitHub, and I will give another post to describe how to setup and run tests on an Android Emulator in GitHub Actions.



Comments

Popular posts from this blog

Physically-Based Rendering in WebGL

Fast subsurface scattering