Google Tests

It is time to dive into some software knowledge.  We're going to start by talking further about the tools described earlier and then get into how to work on the software of our practical project "magic mirror."  So let's start:
Very often programmers, project managers and industry vets will go on about how much cheaper it is to fix a bug earlier in the software development lifecycle.  The thinking goes that if you equate money with the amount of time it takes to fix something multiplied by the number of people required to fix it you arrive at a logarithmic increase of cost for each stage of the development lifecycle.
Over and above the cost of fixing bugs argument, personally, it takes me longer to fix a bug if I have to do a mental context switch and relearn code that I wrote.  The longer the time frame between when I developed code and when I am faced with fixing a bug in it, the harder I feel it is to come to the same level of competence.  Also, maintaining code is a drag and not as fun as new development so if we get it right the first time, there's more time to make something new.
Unit Tests are a great way of finding bugs during the development phase and of discovering side effects in your system.  Imagine writing code, hitting compile, and getting notified that something you just wrote broke a disparate module that has no bearing on your code.  In that scenario, you have what normally would be a very hard bug to fix as you'd be looking in a module that you believe is the problem when really it's code hiding somewhere else.  However, with the benefit of knowing that code just introduced had a side effect in already working code, your attention will be in the right place and hopefully you can avoid lengthy bug hunts.
Now one of the draw backs to unit tests is they take development time to write and most of the time there is no direct benefit, thus making them easy to ignore during time intensive programming sessions and the tech debt associated with having little code coverage increases.  The less you test, the less benefit you'll get in having the capacity to test and the less benefit unit testing is as a whole to your project.  Thus it is very important to understand that unit testing is a commitment that should be made.
That said, let's talk about how to setup unit tests with gradle in android.  We are an embedded development blog after all.
While we could go the easy route and talk about how to use JUnit in Android Studio with its lovely IDE integration, copious documentation, and easy gradle integration.  We'd then be stuck with Java as a programming language and an Android-only code base.  Just as I'd ignore Obj-C in iPhone due to the Apple lock in, I'm going to do the same in Android and even take you down the road of using gradle-experimental which gives an even greater degree of freedom when using C++.
Gradle itself runs on top of the Java Virtual Machine (JVM) which means that it's a language that can be written entirely from Java, with all the benefits and drawbacks of that statement.  However, Java is a very intensively verbose language to choose when writing a project build script.  Thus Gradle is written in a Java dialect called Groovy.  Groovy is a scripting style language that hides a great deal of the complexity in a Java application.  We must learn a little bit of Groovy to write Gradle build files; and that's an exercise for a different blog post.  For now I'm going to assume that we all understand a little bit of Gradle and are only after setting up Google Test to run or we'd have a large and unwieldy topic to cover here.
Google thankfully made our lives relatively easy by including (at long last) googletest in the NDK.  All that is needed to utilize it for android development is to place $(call import-module,third_party/googletest) at the bottom of a make file.  However, our task is made complicated by the fact that Google has yet to support calling make files directly from gradle-experimental, so it is time to break out some Gradle foo of our own and add this useful feature to our project.
Gradle has a testCompile directive that is used in the DSL within the Dependencies section which means that it will only compile the given line if and only if the programmer is running a test build.  Gradle also includes a runCompile directive, for those more adventurous in their GoogleTest integration.  However, for simplicity, we're going to only use the testCompile.
So the first step is to create a new module, then setup the module as a gradle-experimental library by placing this at the top:
apply plugin: ''
Next we need to define the model that is being built
model {
Next we need to tell Android studio to setup the Android model library with the correct configurations:
 android {
CompileSdkVersion and BuildToolsVersion tell gradle to target a certain revision of Android Operating System and which gradle build tools to use.  Notice that here I am pulling from a setting in the overall projects variable list so that if we ever need to change the versions, it can be a simple change and have global impact to the entire project, which could contain multiple modules.
 compileSdkVersion rootProject.ext.compileSdkVersion  buildToolsVersion rootProject.ext.buildToolsVersion
Now we need to setup the defaultConfig with the minimum supported sdk and the target SDK.  The versionCode here identifies which version of the application our module is running at.
defaultConfig.with { minSdkVersion.apiLevel rootProject.ext.minSdkVersion targetSdkVersion.apiLevel rootProject.ext.targetSdkVersion versionCode rootProject.ext.versionCode }
Next we need to define what to do for Release/Debug and here we care about turning off minify phase to ensure that our tests which might be run frequently are built / deployed and tested quickly and as they will never be distributed, why care if we make a large APK?
 buildTypes {   release {     minifyEnabled false   } }
Everything up to now is pretty boilerplate, but now we want to actually do something with our tests.  So we need to define some custom tasks which can call ndk-build which can handle make files for us and give us the ability to have  $(call import-module,third_party/googletest)  at the bottom of the MakeFile. So let's start by taking a look at a make file:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) BaseDir := $(LOCAL_PATH) TestBaseDir := $(LOCAL_PATH)/jniTests LOCAL_CFLAGS += -Werror -Wall LOCAL_CPP_FEATURES := rtti exceptions LOCAL_MODULE := myunittests LOCAL_C_INCLUDES += $(TestBaseDir)/internal LOCAL_C_INCLUDES += $(BaseDir) LOCAL_C_INCLUDES += $(BaseDir)/jni LOCAL_SRC_FILES := $(wildcard $(TestBaseDir)/*.cpp) \      $(wildcard $(TestBaseDir)/internal/*.cpp) LOCAL_SHARED_LIBRARIES := myapp LOCAL_STATIC_LIBRARIES := googletest_main #_static LOCAL_LDLIBS += -landroid -L$(BaseDir)/Android/module/app/build/intermediates/binaries/debug/lib/armeabi-v7a/ -lmyapp LOCAL_LDLIBS += -lz -lc -lm -ldl -llog -lEGL -lGLESv3 -lgnustl_shared include $(BUILD_EXECUTABLE) $(call import-module,third_party/googletest)
There is one important thing to note in this MakeFile: you need to be aware of where your C++ modules under test are placing their final libraries and adjust the LOCAL_LDLIBS line so this process can find it (make modifications for module and app in the path described).  Also add any other module's build path and library name as needed. Next it is also important to have a file just as one normally would for making a NDK project work:
APP_PLATFORM := android-21 APP_ABI := armeabi-v7a APP_STL := gnustl_shared APP_CPPFLAGS += -fexceptions APP_CPPFLAGS += -frtti APP_OPTIM := release
Now in order to make this work normally you'd go to the directory containing the MakeFile and run ndk-build on the command line. And finally we'd deploy the testrunner application to an attached phone via adb, and run the testrunner applciation.  Here's what a typical command line session would look like:
adb push libs/armeabi/ /data/local/tmp/ adb push libs/armeabi/ /data/local/tmp/ adb push libs/armeabi/foo_unittest /data/local/tmp/ adb shell chmod 775 /data/local/tmp/foo_unittest adb shell "LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/foo_unittest"
So let's redo the above in gradle so as to avoid doing those commands every time and can avoid having to remember to run all of the above commands. First let's define a task to build the unit tests:
def ndkBuildTask = tasks.create(name: "ndkBuild", type: Exec, description: "Compile JNI source via NDK") {     def ndkDir = plugins.getPlugin('').sdkHandler.getNdkFolder()     //noinspection GroovyAssignabilityCheck    commandLine "$ndkDir/ndk-build",     "NDK_PROJECT_PATH=build/intermediates/ndk",     "NDK_LIBS_OUT=src/main/jniLibs",     "APP_BUILD_SCRIPT=" + androidMkPath,     "NDK_APPLICATION_MK=" + applicationMkPath }
Next we need a task to call adb and deploy (push) the libraries and unit tests to a folder on android we can universally write to:
def deployTestsTask = tasks.create(name: "deployTests", type: Exec, description: "Copy test runner to active device") {     def sdkDir = plugins.getPlugin('').sdkHandler.getSdkFolder();     // Push the test runner to the device    commandLine "$sdkDir/platform-tools/adb""push",     "build/intermediates/ndk/obj/local/armeabi-v7a/myunittests",    "$deviceTestsFolder/"     doLast {       FileTree libTree = fileTree(dir: 'build/intermediates')       libTree.include '**/armeabi-v7a/**.so'      libTree.each { lib ->           exec {             commandLine "$sdkDir/platform-tools/adb""push", lib,                  "$deviceTestsFolder/"          }       }     // Also ensure the test runner is executable    exec {        commandLine "$sdkDir/platform-tools/adb""shell""chmod""775",                  "$deviceTestsFolder/myunittests"    }   } }
Finally let's run the tests:
def runTestsTask = tasks.create(name: "runTests", type: Exec, description: "Run JNI Tests") {      def stdout = new ByteArrayOutputStream()      def sdkDir = plugins.getPlugin('').sdkHandler.getSdkFolder();      standardOutput = stdout      commandLine "$sdkDir/platform-tools/adb""shell", "LD_LIBRARY_PATH=$deviceTestsFolder""$deviceTestsFolder/myunittests"      println stdout      if (stdout.toString().split("\n").last().contains("FAILED TEST"))      {           throw new GradleException("Test Suite Failed")      } }
And the very last thing is to tie it all together by calling your tasks and setup the dependencies.
tasks.whenTaskAdded { task ->    if ('post')) {        deployTestsTask.dependsOn         runTestsTask.dependsOn deployTestsTask        task.dependsOn runTestsTask    }}
Congratulations on surviving the first of many tips and tricks for working with gradle-experimental. Coming soon is some history and discussion about what Computer Science is and where it comes from.


Popular posts from this blog

Drone VR Software Architecture

Build Snapdragon Flight Drone

Soldering new connectors for the drone