Подтвердить что ты не робот

Android unit test с помощью ant с проектом библиотеки

Похоже, что и последние инструменты SDK для Android все равно не поддерживают тестирование приложений, содержащих связанные проекты библиотек.

У меня есть проект со следующей настройкой:

TestLib (проект библиотеки Android) < - TestMain (проект android) < - TestMainTest (проект android unit test)

Я создал все эти проекты в eclipse, а затем использовал android update (test-/lib-)project ... для создания build.xml et. и др.

Проблема начинается, как только у вас есть класс в TestMain (InheritAddition.java в моем примере), который наследуется от класса в TestLib (Addition.java), и вы хотите ссылаться на этот класс в unit test (InheritAdditionTest.java).

TestLib

public class Addition {
    public int add2(int o1, int o2) {
      return o1 + o2;
    }
}

TestMain

public class InheritAddition extends Addition {
    public int sub(int p1, int p2) {
        return p1 - p2;
    }
}

TestMainTest

public class InheritAdditionTest extends AndroidTestCase {
    public void testSub() {
        Assert.assertEquals(2, new InheritAddition().sub(3, 1));
    }
}

При построении в командной строке результат следующий:

W/ClassPathPackageInfoSource(14871): Caused by: java.lang.NoClassDefFoundError: org/test/main/InheritAddition
W/ClassPathPackageInfoSource(14871):    ... 26 more
W/ClassPathPackageInfoSource(14871): Caused by: java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
W/ClassPathPackageInfoSource(14871):    at dalvik.system.DexFile.defineClass(Native Method)
W/ClassPathPackageInfoSource(14871):    at dalvik.system.DexFile.loadClassBinaryName(DexFile.java:195)
W/ClassPathPackageInfoSource(14871):    at dalvik.system.DexPathList.findClass(DexPathList.java:315)
W/ClassPathPackageInfoSource(14871):    at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:58)
W/ClassPathPackageInfoSource(14871):    at java.lang.ClassLoader.loadClass(ClassLoader.java:501)
W/ClassPathPackageInfoSource(14871):    at java.lang.ClassLoader.loadClass(ClassLoader.java:461)
W/ClassPathPackageInfoSource(14871):    ... 26 more
W/dalvikvm(14871): Class resolved by unexpected DEX: Lorg/test/main/InheritAddition;(0x41356250):0x13772e0 ref [Lorg/test/lib/Addition;] Lorg/test/lib/Addition;(0x41356250):0x13ba910

Я нашел некоторое обходное решение, которое работает для eclipse:

Невозможно создать и запустить проект тестирования Android, созданный с помощью ant create test-project " когда тестируемый проект имеет банки в каталоге libs

Это трюк, но я ищу решение, которое работает с ANT (точнее, я ищу решение, которое работает на обоих одновременно).

Документированный подход (путем изменения build.xml для включения баков из основного проекта в путь класса) здесь не применим, так как примерный проект не использует какие-либо библиотеки (также я считаю, что эта конкретная проблема теперь исправлена с инструментами SDK r16).

Я предполагаю, что решение для переборки состоит в том, чтобы попытаться как-то удалить зависимости TestMainTest до TestLib (путем изменения project.properties), а вместо этого удастся взломать конструкцию script, чтобы поместить эти встроенные банки в путь класса (поэтому замените цель -compile на то, что изменяет путь класса для javac). Поскольку у меня есть долгая история, пытаясь не отставать от изменений привязки Android SDK, это не мой любимый вариант, так как он a) довольно сложный и b) требует постоянной модификации build.xml всякий раз, когда изменяется цепочка инструментов (что вполне часто).

Итак, я ищу идеи о том, как настроить такую ​​настройку без использования кувалды. Возможно, мне не хватает чего-то совершенно очевидного, но для меня этот случай использования довольно стандартный, и мне трудно понять, почему это не поддерживается из коробки.

4b9b3361

Ответ 1

Ответ @wallacen60 хорош. Вчера я пришел к такому же выводу. Тем не менее, существует еще один вариант: вместо исключения lib jar из dexing тестового проекта, было бы неплохо, если бы мы могли найти способ включения lib jar в компиляцию (javac, этап компиляции файла ant ) теста, и только на стадии компиляции, а не на стадии дезинфекции.

Кроме того, решение @wallacen60 вводит большую смысловую разницу между компиляцией проекта 3 и их зависимостями: в приложении Eclipse зависит от lib, тест зависит от приложения. И это правильный способ сделать это. Но в ant оба приложения и теста зависят от Lib и кажутся мне плохим циклом redunduncy.

Итак, на данный момент мы сделали патч файла проекта project.properties, чтобы он включал эту строку:

tested.android.library.reference.1=../SDK_android

И мы модифицировали файл ant тестируемого проекта, так что цель компиляции включает в себя библиотеку: (посмотрите на измененную строку, найдите слово "change" ).

    <!-- override "compile" target in platform android_rules.xml to include tested app external libraries -->
<!-- Compiles this project .java files into .class files. -->
<target name="-compile" depends="-build-setup, -pre-build, -code-gen, -pre-compile">
    <do-only-if-manifest-hasCode elseText="hasCode = false. Skipping...">
        <!-- If android rules are used for a test project, its classpath should include
             tested project location -->
        <condition property="extensible.classpath"
                value="${tested.project.absolute.dir}/bin/classes"
                else=".">
            <isset property="tested.project.absolute.dir" />
        </condition>
        <condition property="extensible.libs.classpath"
                value="${tested.project.absolute.dir}/${jar.libs.dir}"
                else="${jar.libs.dir}">
            <isset property="tested.project.absolute.dir" />
        </condition>
        <echo message="jar libs dir : ${tested.project.target.project.libraries.jars}"/>
        <javac encoding="${java.encoding}"
                source="${java.source}" target="${java.target}"
                debug="true" extdirs="" includeantruntime="false"
                destdir="${out.classes.absolute.dir}"
                bootclasspathref="android.target.classpath"
                verbose="${verbose}"
                classpath="${extensible.classpath}"
                classpathref="jar.libs.ref">
            <src path="${source.absolute.dir}" />
            <src path="${gen.absolute.dir}" />
            <classpath>
                <!-- steff: we changed one line here !-->
                <fileset dir="${tested.android.library.reference.1}/bin/" includes="*.jar"/>
                <fileset dir="${extensible.libs.classpath}" includes="*.jar" />
            </classpath>
            <compilerarg line="${java.compilerargs}" />
        </javac>
               <!-- if the project is instrumented, intrument the classes -->
                        <if condition="${build.is.instrumented}">
                            <then>
                                <echo>Instrumenting classes from ${out.absolute.dir}/classes...</echo>
                                <!-- It only instruments class files, not any external libs -->
                                <emma enabled="true">
                                    <instr verbosity="${verbosity}"
                                           mode="overwrite"
                                           instrpath="${out.absolute.dir}/classes"
                                           outdir="${out.absolute.dir}/classes">
                                    </instr>
                                    <!-- TODO: exclusion filters on R*.class and allowing custom exclusion from
                                         user defined file -->
                                </emma>
                            </then>
                        </if>           
    </do-only-if-manifest-hasCode>
</target>

Действительно, этот механизм кажется правильным, поскольку он имитирует то, что делает затмение. Но eclipse может знать, что приложение зависит от lib при компиляции теста. Единственное отличие состоит в том, что мы вручную разобрали это отношение в ant по строке (в project.properties)

tested.android.library.reference.1=../SDK_android

Но это можно сделать автоматически. Я не могу найти механизм, который используют инструменты google ant для создания refraid пути проекта librairy из инструкции android.library. * В project.properties. Но если бы я мог найти этот механизм, я мог бы распространять эту зависимость в тестовом проекте, как это делает eclipse.

Поэтому я думаю, что лучше всего было бы сообщить google, что у них есть патч, и временно сохранить решение для экспорта вручную зависимости проекта приложения к проекту lib, чтобы скомпилировать тестовый проект.

Может ли кто-нибудь связаться с Google об этой ошибке?

Ответ 2

Использование Android SDK Tools r15 и Eclipse.

Предположим, что вы создаете три проекта в eclipse: Lib (проект библиотеки Android) < - Приложение (проект приложения Android) < - Test (проект android unit test) и определите следующие классы:

[Lib]

public class A {}

[приложение]

public class A extends B {}

[Тест]

public class MyUnitTest extends AndroidTestCase {
    public void test() {
        new A();
        new B();
    }
}

В этой установке TestMain ссылается на TestLib как на библиотеку Android, а TestMainTest имеет ссылку на проект TestMain.

Вы должны увидеть, что Test не компилируется, потому что A не может быть разрешен. Это ожидается, потому что Test не имеет видимости в Lib. Одним из решений является добавление ссылки библиотеки из Test to Lib. Хотя это устраняет проблему компиляции, она ломается во время выполнения. Появляется куча ошибок, но это интересно:

W/dalvikvm( 9275): Class resolved by unexpected DEX: Lcom/example/B;(0x40513450):0x294c70 ref [Lcom/example/A;] Lcom/example/A;(0x40513450):0x8f600
W/dalvikvm( 9275): (Lcom/example/B; had used a different Lcom/example/A; during pre-verification)
W/dalvikvm( 9275): Unable to resolve superclass of Lcom/example/B; (1)
W/dalvikvm( 9275): Link of class 'Lcom/example/B;' failed
E/dalvikvm( 9275): Could not find class 'com.example.B', referenced from method com.example.test.MyUnitTest.test
W/dalvikvm( 9275): VFY: unable to resolve new-instance 3 (Lcom/example/B;) in Lcom/example/test/MyUnitTest;
D/dalvikvm( 9275): VFY: replacing opcode 0x22 at 0x0000
D/dalvikvm( 9275): VFY: dead code 0x0002-000a in Lcom/example/test/MyUnitTest;.test ()V

Это связано с тем, что проекты Test и App ссылаются на проект библиотеки Lib, поэтому оба получаемых apks включают копию com.example.A.

Не добавлять явные зависимости в eclipse из тестового проекта в проект библиотеки (если эта библиотека является зависимой от тестируемого проекта приложения). Это может привести как к приложениям, так и к тестовым проектам включить экземпляры одних и тех же классов в их получаемые apks, и тест будет терпеть неудачу во время выполнения.

Нам нужно найти путь к проблеме видимости времени компиляции. Во время выполнения Test будет иметь видимость в App и, следовательно, классы в Lib. Вместо того, чтобы создавать ссылку на библиотеку из Test to Lib, обновите путь сборки приложения для экспорта своих проектов библиотеки. Теперь успешно выполняются тестовые компиляции и unit test.

В Eclipse, чтобы протестировать проект приложения, который ссылается на проект библиотеки, экспортируйте проекты библиотеки из проекта приложения в настройках пути сборки.

Теперь все работает в Eclipse, но что касается Ant? Используйте команды проекта android update [lib- | test-] для создания необходимых файлов build.xml. Обязательно запустите Ant clean во всех трех каталогах: Lib, App и Test. Невозможность очистить все три проекта может привести к успешному компиляции.

Компиляция Ant завершится с ошибкой:

[javac] ...Test/src/com/example/test/MyUnitTest.java:3: cannot find symbol
[javac] symbol  : class A
[javac] location: package com.example
[javac] import com.example.A;
[javac]                   ^
[javac] ...Test/src/com/example/test/MyUnitTest.java:10: cannot access com.example.A
[javac] class file for com.example.A not found
[javac]         new B();
[javac]         ^
[javac] ...Test/src/com/example/test/MyUnitTest.java:11: cannot find symbol
[javac] symbol  : class A
[javac] location: class com.example.test.MyUnitTest
[javac]         new A();
[javac]             ^
[javac] 3 errors

Почему сбой сборки Ant при завершении сборки Eclipse? Системы сборки Eclipse и Ant различны. Экспорт проектов библиотеки из приложения в Eclipse не влияет на сборку Ant. Сборка завершилась неудачно, потому что проект Test не имеет видимости в проекте Lib. Если мы попытаемся решить эту проблему, добавив свойство android.library.refernce в Test/project.properties, мы сделали то же самое, что и добавление ссылки библиотеки из Test to Lib в Eclipse. Конструкция Ant будет успешной, но тест завершится неудачно во время выполнения со знакомой ошибкой "Класс, разрешенный неожиданным DEX".

Нам нужен способ, чтобы тестовый проект мог компилироваться против проекта библиотеки, но не включать его в процесс dexing. Для этого есть два шага. Сначала включите ссылку из Test to Lib, которая не влияет на Eclipse. Во-вторых, обновите систему сборки Ant, чтобы библиотека была скомпилирована, но исключена из dexing.

В верхней части Test/build.xml я определяю свойство, которое указывает на библиотеку. Это похоже на добавление ссылки на Test/project.properties, за исключением того, что Eclipse не увидит его:

Теперь нам нужно исключить библиотеку jar из процесса dexing. Это требует обновления макроса dex-helper. Я помещаю макрос переопределения после строки в файле Test/build.xml. Новый помощник dex исключает все файлы jar не в дереве папок тестового проекта из процесса удаления:

<macrodef name="dex-helper">
  <element name="external-libs" optional="yes"/>
  <attribute name="nolocals" default="false"/>
  <sequential>
    <!-- sets the primary input for dex. If a pre-dex task sets it to
                 something else this has no effect -->
    <property name="out.dex.input.absolute.dir" value="${out.classes.absolute.dir}"/>
    <!-- set the secondary dx input: the project (and library) jar files
                 If a pre-dex task sets it to something else this has no effect -->
    <if>
      <condition>
        <isreference refid="out.dex.jar.input.ref"/>
      </condition>
      <else>
        <!--
                        out.dex.jar.input.ref is not set. Compile the list of jars to dex.
                        For test projects, only dex jar files included in the project
                        path
                    -->
        <if condition="${project.is.test}">
          <then>
            <!-- test project -->
            <pathconvert pathsep="," refid="jar.libs.ref" property="jars_to_dex_pattern"/>
            <path id="out.dex.jar.input.ref">
              <files includes="${jars_to_dex_pattern}">
                <!-- only include jar files actually in the test project -->
                <filename name="${basedir}/**/*"/>
              </files>
            </path>
            <property name="in_jars_to_dex" refid="jar.libs.ref"/>
            <property name="out_jars_to_dex" refid="out.dex.jar.input.ref"/>
            <echo message="Test project! Reducing jars to dex from ${in_jars_to_dex} to ${out_jars_to_dex}."/>
          </then>
          <else>
            <path id="out.dex.jar.input.ref"/>
          </else>
        </if>
      </else>
    </if>
    <dex executable="${dx}" output="${intermediate.dex.file}" nolocals="@{nolocals}" verbose="${verbose}">
      <path path="${out.dex.input.absolute.dir}"/>
      <path refid="out.dex.jar.input.ref"/>
      <external-libs/>
    </dex>
  </sequential>
</macrodef>

С учетом этих изменений Test строит и запускает как Eclipse, так и Ant.

Счастливое тестирование!

Другие примечания: Если в Eclipse ничего не строится, и вы уверены, что должны, попробуйте обновить проекты в следующем порядке: Lib, App, Test. Мне часто приходится делать это после изменения пути построения. Мне также иногда приходится строить чистые вещи для правильной работы.

Ответ 3

Ответы, размещенные здесь, немного запутаны. Я не уверен, что это было недавно изменено, но в r15 можно просто добавить следующую строку в свои project.properties и все будет построено. Вам не нужно изменять какие-либо скрипты сборки.

android.library.reference.1=../lib_project

Я предполагаю, что он не ищет рекурсивные проекты библиотеки, поэтому вам просто нужно ссылаться на него в своем тестовом проекте, если вы ссылаетесь на него в тестируемом проекте.