Unit test 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)
Как мне найти путь к родным .so-библиотекам, который находится в src/main/libs
для unit test без ошибок?
Примечание: внутри каталога src/main/libs
У меня есть еще 3 подкаталога: armeabi
, mips
и x86
. Каждый из них содержит правильный .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());
}
Ответы
Ответ 1
Единственное решение, которое я нашел, что работает без хаков, - это использовать JUnit с помощью инструментального тестирования (каталог androidTest).
Мой класс теперь может быть протестирован отлично, но с помощью устройства или эмулятора Android.
Ответ 2
Я не уверен, решает ли это вашу проблему или нет, но пока никто не упомянул о шаблоне стратегии для работы с библиотекой предзагрузок классов во время их создания.
Посмотрим на пример:
Мы хотим реализовать класс решателя Fibonacci. Предполагая, что мы обеспечили реализацию в собственном коде и сумели создать собственную библиотеку, мы можем реализовать следующее:
public interface Fibonacci {
long calculate(int steps);
}
Во-первых, мы предоставляем нашу собственную реализацию:
public final class FibonacciNative implements Fibonacci {
static {
System.loadLibrary("myfibonacci");
}
public native long calculate(int steps);
}
Во-вторых, мы предоставляем реализацию Java для решения Fibonacci:
public final class FibonacciJava implements Fibonacci {
@Override
public long calculate(int steps) {
if(steps > 1) {
return calculate(steps-2) + calculate(steps-1);
}
return steps;
}
}
В-третьих, мы завершаем решателей родительским классом, выбирая его собственную реализацию во время его создания:
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
Просто убедитесь, что каталог, содержащий библиотеку, содержится в системном свойстве java.library.path
.
Из теста вы можете установить его перед загрузкой библиотеки:
System.setProperty("java.library.path", "... path to the library .../libs/x86");
Вы можете указать путь жестко закодированным, но это сделает проект менее переносимым для других сред. Поэтому я предлагаю вам создать его программно.
Ответ 4
Там есть способ настройки пути библиотеки Gradle -run VM для локальных тестов, и я расскажу об этом ниже, но спойлер: в моем эксперименте @ThanosFisherman право: тесты локальных блоков для вещей, которые используют Android NDK, кажутся безумными. Теперь
Итак, для всех, кто ищет способ загрузки общих (например, .so
) библиотек в модульные тесты с помощью gradle, здесь несколько длинный реферат:
Цель состоит в том, чтобы установить путь поиска общей библиотеки для JVM, выполняющей модульные тесты.
Althoug Многие люди предлагают положить путь 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 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
Для плагина Android Studio JUnit вы можете указать параметры виртуальной машины и переменные среды в настройках тестовой конфигурации, поэтому просто запустите JUnit-тест (щелкните правой кнопкой мыши на методе тестирования или что-то еще), а затем отредактируйте Run Configuration:
![введите описание изображения здесь]()
Хотя это звучит как "выполняемая миссия", я обнаружил, что при использовании libc.so, libm.so
и других из моего os /usr/lib
мне дают ошибки в версии (возможно, потому что моя собственная библиотека скомпилирована cmake с помощью инструментария android ndk против него платформы libs). И использование платформы libs из пакетов ndk привело к ошибке JVM с ошибкой SIGSEGV
(из-за несовместимости библиотек платформы ndk с окружением хоста os)
Обновить. Как @AlexCohn резко указал в комментариях, нужно построить против lib среды для хоста, чтобы это работало; даже если ваша машина, скорее всего, x86_64, двоичные файлы x86_64, созданные против среды NDK, не будут выполняться.
Возможно, я кое-что упустил из виду, и я буду благодарен за любую обратную связь, но на данный момент я отбросил всю идею в пользу инструментальных тестов.
Ответ 5
Файлы .so должны быть помещены под
SRC/главная/jniLibs
Не под src/main/libs
(Протестировано с помощью Android Studio 1.2.2)
Для справки проверьте страницу http://ph0b.com/android-studio-gradle-and-ndk-integration/, хотя некоторые части могут быть устаревшими.
Ответ 6
Попробуйте запустить тестовый код с параметром java -XshowSettings: properties и убедитесь, что ваш путь назначения для системных библиотек и в результате вывода этой команды значения пути к библиотеке совпадают.
Ответ 7
Если для теста требуется библиотека, используйте тест AndroidTest (в разделе src/androidTest/...
), а не тест джунита. Это позволит вам загружать и использовать нативную библиотеку, как и в других местах вашего кода.
Если библиотека не требуется для вашего теста, просто оберните загрузку системы в try/catch. Это позволит классу JNI по-прежнему работать в тестах junit (в src/test/...
), и это безопасный обходной путь, учитывая, что он вряд ли замаскирует ошибку (что-то еще, безусловно, завершится ошибкой, если родная библиотека на самом деле нужно). Оттуда вы можете использовать что-то вроде mockito, чтобы заглушить любые вызовы методов, которые все еще попадают в библиотеку JNI.
Например в Котлине:
companion object {
init {
try {
System.loadLibrary("mylibrary")
} catch (e: UnsatisfiedLinkError) {
// log the error or track it in analytics
}
}
}