在 Android JUnit 测试中加载本机库

2022-09-01 19:23:58

我已经生成了一个本机库,我可以在我的Android应用程序中加载和使用。但是,我想针对应用的这一部分编写一些测试。ndk-build

在测试中调用本机函数时,我收到以下异常消息:

java.lang.UnsatisfiedLinkError: no process in java.library.path

...哪里是我要导入的本机库,名为 .processlibprocess.so

我正在使用Roboelectric进行测试,如果有所作为,请使用 运行这个特定的测试。RobolectricTestRunner

如何让我的测试项目“查看”本机库?


编辑:我正在我的应用程序中加载库,如下所示:

static {
    System.loadLibrary("process");
}
public static native int[] process(double[][] data);

调用在应用中工作正常(库已加载),但在从测试运行时失败,上面给出的例外。Process.process(array)


编辑 2:如果我设置为 VM 参数,则:-Djava.library.path="<the directory of libprocess.so>"

System.out.println(System.getProperty("java.library.path"));

确实显示了我设置的路径,但我仍然得到相同的异常。我将目录设置为:

<project-name>/libs/x86

...但作为一条绝对的路径。


答案 1

对于任何仍在寻找的人来说,blork的想法是正确的 - 你需要为你的“原生”平台(Windows,Linux,Mac)编译你的原生库。Android NDK 为 Android 平台构建库(.so 文件 - 也可能在 Linux 上运行),这就是为什么在活动测试用例中运行没有问题的原因(因为它加载了 Android 实例)。

要运行低级的、快速的 JUnit 测试,您需要支持 JVM。在Windows上,这可能是在构建DLL,在Apple上,它是在构建dylibs(假设共享库)。

我刚刚在我的android-ndk-swig-example存储库(https://github.com/sureshjoshi/android-ndk-swig-example/issues/9)中完成了一个示例。

基本上,在我的CMakeLists中,我添加了一个Apple警告:

# 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})

然后,我确保 Gradle 运行单元测试,但使用 Mac 构建系统(不是 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
    }
 }

注意时间!!!

使用此方法时,从技术上讲,您不是在测试 NDK 生成的库。您正在测试相同的代码,但使用不同的编译器(msvc,xcode,gcc,clang,您在主机上使用的任何内容)进行编译。

实际上,这意味着大多数测试结果都是有效的 - 除非您遇到由每个编译器的怪癖或STL实现等引起的问题。这并不像10多年前那么糟糕,但你不能100%肯定地说,使用主机库的JUnit测试结果与Android库相同。不过,你可以说它相当接近。

再说一遍,除非您使用Android NDK为每个受支持的架构运行本机单元测试,否则您也不能说任何关于确定性的事情......所以从中得到你想要的。

一种过度的方法(但如果自动化,真的很酷)是编写原生单元测试,无论你如何做它们(Google Test,Catch等),然后编译并运行你的原生库和单元测试,每个架构都有Android NDK。这为您的 C/C++覆盖了潜在的目标体系结构。

从这里,您可以将上述主机库与 JUnit 结合使用,以快速单元测试与本机库交互的 JNI 层。在CI系统中,您可能仍然应该运行这些相同的单元测试 - 但作为Android Instrumentation测试(或其他运行模拟Android环境的测试)。

与所有事情一样,无论您在哪里拥有接口,都可以创建模拟 - 但是在某些时候,您也需要系统/功能/集成测试。

更新:

在博客文章中对上述内容进行更全面的解释(http://www.sureshjoshi.com/mobile/android-junit-native-libraries/)


答案 2

我已经切换到默认的测试风格(使用ActiveUnitTestCase而不是RoboElectric),它现在运行良好。很遗憾,我不得不牺牲测试运行的速度,但是在模拟器上运行测试实际上是有效的。您还可以为 JNI 类创建一个影子类,如下所述:

加载 JNI 库的应用程序对象上的 Robolectric 坦克。我可以获得解决方法吗?

也许为我的机器编译库会起作用,但我不能再花时间在上面了。


推荐