web-dev-qa-db-ja.com

ユニットテストJavaネイティブライブラリをロードするクラス

Android Studioで単体テストを実行しています。次のコードでネイティブライブラリをロードするJavaクラスがあります

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

しかし、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)

エラーなしで単体テストを行うために、src/main/libsにあるネイティブ.soライブラリのパスを見つけるにはどうすればよいですか?

注:src/main/libsディレクトリ内には、armeabimipsx86の3つのサブディレクトリがあります。それぞれに適切な.soファイルが含まれています。 NDKライブラリのビルドに非実験バージョンを使用しています。

私は他のすべての「純粋な」Javaクラスはユニットテストでうまくテストできるので、他のサードパーティのテストライブラリを使いたくありません。

ここにエラーをスローするテストコードがあります

   @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());
    }
51
ThanosFisherman

ハッキングなしで動作することがわかった唯一の解決策は、インストルメンテーションテスト(androidTestディレクトリ)でJUnitを使用することです。これで、クラスを正常にテストできますが、Androidデバイスまたはエミュレーターを使用します。

10
ThanosFisherman

これがあなたの問題を解決するかどうかはわかりませんが、これまでのところ、作成中にライブラリをプリロードするクラスを扱うための戦略パターンについては誰も言及していません。

例を見てみましょう:

フィボナッチソルバークラスを実装します。ネイティブコードで実装を提供し、ネイティブライブラリを生成できたと仮定すると、以下を実装できます。

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

まず、ネイティブ実装を提供します。

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

    public native long calculate(int steps);
}

次に、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;
   }
}

3番目に、インスタンス化中に独自の実装を選択する親クラスでソルバーをラップします。

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

}

したがって、戦略を使用してライブラリへのパスを見つけることに関する問題。ただし、テスト中にネイティブライブラリを含める必要がある場合、この場合は問題を解決できません。ネイティブライブラリがサードパーティのライブラリである場合、問題は解決されません。

基本的に、これはJavaコードのネイティブコードをモックアウトすることにより、ネイティブライブラリのロードの問題を回避します。

これが何らかの形で役立つことを願っています:)

3
dawid gdanski

is Gradle-runのライブラリパスを設定する方法VMローカルユニットテスト用です。以下で説明しますが、ネタバレです。 :私の経験では、@ ThanosFishermanは正しいです。Android NDKは馬鹿用事のようです。

したがって、gradleを使用して共有(つまり.so)ライブラリを単体テストにロードする方法を探している人のために、やや長い要約を次に示します。

目標は、単体テストを実行するJVMの共有ライブラリ検索パスを設定することです。

多くの人がlibパスをJava.library.pathに入れることを提案していますが、少なくとも私のLinuxマシンではが動作しないであることがわかりました。 (また、 このCodeRanchスレッドと同じ結果

が機能するものは、LD_LIBRARY_PATH os環境変数を設定しています(またはPATHはWindowsで最も近い同義語です)

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

それにより、実行することができます、例えば./gradlew :mymodule:testDebugUnitTestとネイティブライブラリは、指定したパスで検索されます。

Android Studio JUnit plugin を使用してAndroid StudioのJUnitプラグインでは、VMオプションとテスト構成の設定の環境変数なので、JUnitテストを実行し(テストメソッドなどを右クリックして)、実行構成を編集します: enter image description hereenter image description here

「ミッション達成」のように聞こえますが、libc.so, libm.soおよびその他のosの/usr/libを使用すると、バージョンエラーが発生することがわかりました(おそらく、自分のライブラリがAndroid独自のプラットフォームライブラリに対するndkツールキット)。また、ndkパッケージのプラットフォームライブラリを使用すると、JVMがSIGSEGVエラー(ホストos環境との互換性がないため)を引き起こしました

Update コメントで@AlexCohnが鋭く指摘したように、これが機能するためにはホスト環境ライブラリに対してビルドする必要があります。マシンがx86_64である可能性が最も高い場合でも、NDK環境に対してビルドされたx86_64バイナリは機能しません。

もちろん、私が見落としていたものがあるかもしれませんし、フィードバックに感謝しますが、今のところ、計装されたテストに賛成して全体のアイデアを捨てています。

1
Ivan Bartsov

ライブラリを含むディレクトリがJava.library.pathシステムプロパティ。

テストから、ライブラリをロードする前に設定できます。

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

ハードコーディングされたパスを指定できますが、これによりプロジェクトは他の環境に移植できなくなります。したがって、プログラムで構築することをお勧めします。

1
Henry

Java -XshowSettings:propertiesオプションを使用してテストコードを実行し、システムライブラリの宛先パスと、このコマンドの出力でライブラリパスの値が同じであることを確認してください。

0
daemonThread

.soファイルは下に配置されます

src/main/jniLibs

Src/main/libsの下にない

(Android Studio 1.2.2)でテスト済み)

参照については、ページを確認してください- http://ph0b.com/Android-studio-gradle-and-ndk-integration/ 、ただし一部の部分は古くなっている可能性があります。

0
Prabindh

ライブラリがテストに必要な場合、AndroidTestを使用します(src/androidTest/...)junitテストではなく。これにより、コードの他の場所で行うように、ネイティブライブラリをロードして使用できます。

ライブラリがテストに必要ない場合、システム負荷をtry/catchでラップするだけです。これにより、JNIクラスは引き続きjunitテストで動作します(src/test/...)そして、エラーをマスクする可能性が低いことを考えると、安全な回避策です(ネイティブlibが実際に必要な場合、他の何かが確実に失敗します)。そこから、mockitoなどを使用して、JNIライブラリにヒットするメソッド呼び出しをスタブできます。

たとえば、Kotlinの場合:

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