单元测试加载本机库的 Java 类

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34802667/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 23:22:40  来源:igfitidea点击:

Unit test Java class that loads native library

javaandroidunit-testingjunit4

提问by ThanosFisherman

I'm running unit tests in Android Studio. I have a Java class that loads a native library with the following code

我正在 Android Studio 中运行单元测试。我有一个 Java 类,它使用以下代码加载本机库

 static
    {
       System.loadLibrary("mylibrary");
    }

But when I test this class inside my src/testdirectory I get

但是当我在我的src/test目录中测试这个类时,我得到

java.lang.UnsatisfiedLinkError: no mylibrary in java.library.path
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1864)
    at java.lang.Runtime.loadLibrary0(Runtime.java:870)
    at java.lang.System.loadLibrary(System.java:1122)

How can I make it find the path of native .so libraries which is located at src/main/libsin order to unit test without errors?

我怎样才能让它找到位于的本地 .so 库的路径,src/main/libs以便单元测试没有错误?

Note: inside src/main/libsdirectory I have 3 more subdirectories: armeabi, mipsand x86. Each one of those contains the proper .so file. I'm using the Non experimental versionfor building NDK libs.

注意:在src/main/libs目录内我还有 3 个子目录:armeabi,mipsx86. 每一个都包含正确的 .so 文件。我正在使用非实验版本来构建 NDK 库。

I don't wanna use other 3rd party testing libraries as all my other "pure" java classes can be unit tested fine. But if that's not possible then I'm open to alternatives.

我不想使用其他 3rd 方测试库,因为我所有其他的“纯”java 类都可以很好地进行单元测试。但如果这是不可能的,那么我愿意接受替代方案。

Here is my test code which throws the error

这是我的测试代码,它抛出错误

   @Test
    public void testNativeClass() throws Exception
    {
        MyNativeJavaClass test = new MyNativeJavaClass("lalalal")
        List<String> results = test.getResultsFromNativeMethodAndPutThemInArrayList();
        assertEquals("There should be only three result", 3, results.size());
    }

采纳答案by ThanosFisherman

The only solution I found that works without hacks is to use JUnit through instrumentation testing (androidTest directory). My class can now be tested fine but with help of the android device or emulator.

我发现的唯一解决方案是通过仪器测试(androidTest 目录)使用 JUnit。现在可以很好地测试我的课程,但需要借助 android 设备或模拟器。

回答by dawid gdanski

I am not sure whether this solves your problem or not but so far nobody has mentioned about strategy pattern for dealing with classes preloading library during their creation.

我不确定这是否能解决您的问题,但到目前为止,没有人提到在创建过程中处理类预加载库的策略模式。

Let's see the example:

让我们看看这个例子:

We want to implement Fibonacci solver class. Assuming that we provided implementation in the native code and managed to generate the native library, we can implement the following:

我们想要实现斐波那契求解器类。假设我们在本机代码中提供了实现并设法生成了本机库,我们可以实现以下内容:

public interface Fibonacci {
     long calculate(int steps);
}

Firstly, we provide our native implementation:

首先,我们提供我们的原生实现:

public final class FibonacciNative implements Fibonacci {
    static {
      System.loadLibrary("myfibonacci");
    }

    public native long calculate(int steps);
}

Secondly, we provide Java implementation for Fibonacci solver:

其次,我们提供斐波那契求解器的 Java 实现:

public final class FibonacciJava implements Fibonacci {

   @Override
   public long calculate(int steps) {
       if(steps > 1) {
           return calculate(steps-2) + calculate(steps-1);
       }
       return steps;
   }
}

Thirdly, we wrap the solvers with parental class choosing its own implementation during its instantiation:

第三,我们用父类包装求解器,在其实例化期间选择自己的实现:

public class FibonnaciSolver implements Fibonacci {

   private static final Fibonacci STRATEGY;

   static {
      Fibonacci implementation;
      try {
         implementation = new FibonnaciNative();
      } catch(Throwable e) {
         implementation = new FibonnaciJava();
      }

      STRATEGY = implementation;
   }

   @Override
   public long calculate(int steps) {
       return STRATEGY.calculate(steps);
   }

}

Thus, the problem with finding path to the library using strategy. This case, however, does not resolve the problem if the native library is really necessary to be included during the test. It does not neither solve the problem if the native library is a third-party library.

因此,使用策略查找库路径的问题。但是,如果在测试期间确实需要包含本机库,则这种情况并不能解决问题。如果本机库是第三方库,它也不能解决问题。

Basically, this gets around the native library load problem by mocking out the native code for java code.

基本上,这通过模拟 Java 代码的本机代码来解决本机库加载问题。

Hope this helps somehow:)

希望这有帮助:)

回答by Ivan Bartsov

There isa way to configure library path of Gradle-run VM for local unit tests, and I'm going to describe it below, but spoiler: in my expericence, @ThanosFisherman is right: local unit tests for stuff that uses the Android NDK seem to be a fools errand right now.

一种为本地单元测试摇篮运行虚拟机的配置库路径,我要去下面形容它,但扰流板:在我的实际经验,@ThanosFisherman是正确的:即采用了Android NDK的东西本地单元测试现在似乎是一个傻瓜差事。

So, for anyone else looking for a way to load shared (i.e. .so) libraries into unit tests with gradle, here's the somewhat lengthy abstract:

因此,对于任何其他人正在寻找一种.so使用 gradle将共享(即)库加载到单元测试中的方法,这里有一些冗长的摘要:

The goal is to set the shared library lookup path for the JVM running the unit tests.

目标是为运行单元测试的 JVM 设置共享库查找路径。

Althoug many people suggest putting the lib path into java.library.path, I found that it doesn't work, at least not on my linux machine. (also, same results in this CodeRanch thread)

尽管很多人建议将 lib 路径放入java.library.path,但我发现它不起作用,至少在我的 linux 机器上不起作用。(此外,此 CodeRanch 线程中的结果相同

What does workthough is setting the LD_LIBRARY_PATHos environment variable (or PATHis the closest synonym in Windows)

什么工作虽然是设置LD_LIBRARY_PATHos 环境变量(或者PATH是 Windows 中最接近的同义词)

Using Gradle:

使用摇篮:

// module-level build.gradle
apply plugin: 'com.android.library' // or application

android {
    ...

    testOptions {
        unitTests {
            all {
                // This is where we have access to the properties of gradle's Test class,
                // look it  up if you want to customize more test parameters

                // next we take our cmake output dir for whatever architecture
                // you can also put some 3rd party libs here, or override
                // the implicitly linked stuff (libc, libm and others)

                def libpath = '' + projectDir + '/build/intermediates/cmake/debug/obj/x86_64/'
                    +':/home/developer/my-project/some-sdk/lib'

                environment 'LD_LIBRARY_PATH', libpath
            }
        }
    }
}

With that, you can run, e.g. ./gradlew :mymodule:testDebugUnitTestand the native libs will be looked for in the paths that you specified.

有了它,您可以运行,例如./gradlew :mymodule:testDebugUnitTest,将在您指定的路径中查找本机库。

Using Android Studio JUnit pluginFor the Android Studio's JUnit plugin, you can specify the VM options and the environment variables in the test configuration's settings, so just run a JUnit test (right-clicking on a test method or whatever) and then edit the Run Configuration: enter image description hereenter image description here

使用 Android Studio JUnit 插件对于 Android Studio 的 JUnit 插件,您可以在测试配置的设置中指定 VM 选项和环境变量,因此只需运行 JUnit 测试(右键单击测试方法或其他),然后编辑运行配置: 在此处输入图片说明在此处输入图片说明

Although it sounds like "mission accomplished", I found that when using libc.so, libm.soand others from my os /usr/libgives me version errors (probably because my own library is compiled by cmake with the android ndk toolkit against it's own platform libs). And using the platform libs from the ndk packages brought down the JVM wih a SIGSEGVerror (due to incompatibility of the ndk platform libs with the host os environment)

虽然听起来像是“任务完成”,但我发现在使用libc.so, libm.so我的操作系统中的其他人时/usr/lib会给我版本错误(可能是因为我自己的库是由 cmake 使用 android ndk 工具包针对它自己的平台库编译的)。并且使用 ndk 包中的平台库会导致 JVM 出现SIGSEGV错误(由于 ndk 平台库与主机操作系统环境不兼容)

UpdateAs @AlexCohn incisively pointed out in the comments, one has to build against the host environment libs for this to work; even though your machine most likely is x86_64, the x86_64binaries built against NDK environment will not do.

更新正如@AlexCohn 在评论中尖锐指出的那样,必须针对主机环境库进行构建才能使其正常工作;即使您的机器很可能是x86_64,针对 NDK 环境构建的x86_64二进制文件也行不通。

There may be something I overlooked, obviously, and I'll appreciate any feedback, but for now I'm dropping the whole idea in favor of instrumented tests.

显然,我可能忽略了一些东西,我会感谢任何反馈,但现在我放弃了整个想法,转而支持仪器化测试。

回答by gMale

If the library is required for your test, use an AndroidTest (under src/androidTest/...) rather than a junit test. This will allow you to load and use the native library like you do elsewhere in your code.

如果您的测试需要该库,请使用 AndroidTest(在 下src/androidTest/...)而不是 junit 测试。这将允许您像在代码中的其他地方一样加载和使用本机库。

If the library is not required for your test, simply wrap the system load in a try/catch. This will allow the JNI class to still work in junit tests (under src/test/...) and it is a safe workaround, given that it is unlikely to mask the error (something else will certainly fail, if the native lib is actually needed). From there, you can use something like mockito to stub out any method calls that still hit the JNI library.

如果您的测试不需要该库,只需将系统负载包装在 try/catch 中。这将允许 JNI 类仍然在 junit 测试中工作(在 下src/test/...),并且这是一种安全的解决方法,因为它不太可能掩盖错误(如果确实需要本机库,其他东西肯定会失败)。从那里,您可以使用类似 mockito 的东西来排除仍然命中 JNI 库的任何方法调用。

For example in Kotlin:

例如在 Kotlin 中:

    companion object {
        init {
            try {
                System.loadLibrary("mylibrary")
            } catch (e: UnsatisfiedLinkError) {
                // log the error or track it in analytics
            }
        }
    }

回答by Henry

Just make sure, the directory containing the library is contained in the java.library.pathsystem property.

只要确保包含库的目录包含在java.library.path系统属性中。

From the test you could set it before you load the library:

从测试中,您可以在加载库之前设置它:

System.setProperty("java.library.path", "... path to the library .../libs/x86");

You can specify the path hard coded, but this will make the project less portable to other environments. So I suggest you build it up programmatically.

您可以指定硬编码的路径,但这会降低项目对其他环境的可移植性。所以我建议你以编程方式构建它。

回答by Prabindh

The .so files are to be placed under

.so 文件将被放置在

src/main/jniLibs

源代码/主/jniLibs

Not under src/main/libs

不在 src/main/libs 下

(Tested with Android Studio 1.2.2)

(使用 Android Studio 1.2.2 测试)

For reference check the page - http://ph0b.com/android-studio-gradle-and-ndk-integration/, though some portions might be outdated.

作为参考,请查看页面 - http://ph0b.com/android-studio-gradle-and-ndk-integration/,尽管某些部分可能已过时。

回答by daemonThread

Try running your test code with java -XshowSettings:properties option and make sure your destination path for system libraries and in the output of this command, library path values are the same

尝试使用 java -XshowSettings:properties 选项运行测试代码,并确保系统库的目标路径和此命令的输出中的库路径值相同