I\'ve generated a native library using ndk-build
which I\'m able to load and use with in my Android application. However, I want to write some tests against thi
For anyone still looking, blork had the right idea - you need to compile your native libraries for your 'native' platform (Windows, Linux, Mac). The Android NDK builds libraries for the Android platform (.so files - might also work on Linux), and this is why there are no issues running in Activity Test Cases (because it loads up an Android instance).
To get the low-level, hella fast JUnit tests running, you need to support your JVM. On Windows, this might be building DLLs, on Apple, it's building dylibs (assuming shared libraries).
I've just completed a sample in my android-ndk-swig-example repo (https://github.com/sureshjoshi/android-ndk-swig-example/issues/9).
Basically, in my CMakeLists, I added an Apple caveat:
# Need to create the .dylib and .jnilib files in order to run JUnit tests
if (APPLE)
# Ensure jni.h is found
find_package(JNI REQUIRED)
include_directories(${JAVA_INCLUDE_PATH})
And then I make sure Gradle runs for unit tests, but using the Mac build system (not NDK).
def osxDir = projectDir.absolutePath + '/.externalNativeBuild/cmake/debug/osx/'
task createBuildDir() {
def folder = new File(osxDir)
if (!folder.exists()) {
folder.mkdirs()
}
}
task runCMake(type: Exec) {
dependsOn createBuildDir
workingDir osxDir // Jump to future build directory
commandLine '/usr/local/bin/cmake' // Path from HomeBrew installation
args '../../../../' // Relative path for out-of-source builds
}
task runMake(type: Exec) {
dependsOn runCMake
workingDir osxDir
commandLine 'make'
}
project.afterEvaluate {
// Not sure how much of a hack this is - but it allows CMake/SWIG to run before Android Studio
// complains about missing generated files
// TODO: Probably need a release hook too?
javaPreCompileDebug.dependsOn externalNativeBuildDebug
if (org.gradle.internal.os.OperatingSystem.current().isMacOsX()) {
javaPreCompileDebugAndroidTest.dependsOn runMake
}
}
CAVEAT TIME!!!
When you use this method, you're technically not testing the NDK-generated libs. You're testing the same code, but compiled using a different compiler (msvc, xcode, gcc, clang, whatever you use on the host).
What this means practically, is that most of the test results will be valid - except when you run into problems caused by each compiler's quirks, or STL implementations, etc... This isn't as bad as it was 10+ years ago, but you can't say with 100% certainty that the results of JUnit testing with host libs is identical to the Android libs. You can say that it's reasonably close, though.
Then again, unless you're running your native unit tests using the Android NDK for each supported architecture, you also can't say anything about certainty either... So take what you will from it.
An overkill approach (but really cool if automated) would be to write your native unit tests however you do them (Google Test, Catch, etc), then compile and run your native libs and unit tests with the Android NDK per each architecture. This provides your C/C++ coverage across your potential target architectures.
From here, you could use the aforementioned host libs with JUnit to rapidly unit test your JNI layer interacting with your native lib. In your CI system, you should still probably run these same unit tests - but as Android Instrumentation tests (or something else that runs an emulated Android environment).
As with everything, wherever you have an interface, you can create mocks - but at some point, you'll need system/functional/integration tests too.
Update:
More comprehensive explanation of above in a blog article (http://www.sureshjoshi.com/mobile/android-junit-native-libraries/)
I've switched to the default testing style (using ActivityUnitTestCase instead of RoboElectric) and it's now running fine. It's a shame I have to sacrifice the speed of test running, but running the tests on the emulator actually works. You could also create a shadow class for the JNI class, as detailed here:
Robolectric tanks on Application objects that load JNI libraries. Can I get a workaround?
Perhaps compiling the library for my machine would have worked, but I couldn't spend any more time on it.
Just in case, for anyone else looking for the solutions to this, there is a way to properly set the lib lookup path for JUnit tests (although I still haven't managed to successfully run the tests, different set of issues):
You need to put the path in the LD_LIBRARY_PATH
(or PATH
on Windows) environment variable. I had the exact same experience: I set the java.library.path
first, but seemingly it doesn't affect the native loader's behavior in any way.
You're welcome to take a look at my answer here if you need details on how to do that with Gradle/Android Studio