Модульный тест Android с использованием ant с библиотечным проектом - PullRequest
21 голосов
/ 21 декабря 2011

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

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

TestLib (проект библиотеки Android) <- TestMain (проект Android) <- TestMainTest (проект модульного тестирования Android) </p>

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

Проблема начинается, как только у вас есть класс в TestMain (InheritAddition.java в моем примере), который наследуется от класса в TestLib (Addition.java), и вы хотите сослаться на этот класс в модуле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""когда в тестируемом проекте есть jar-файлы в каталоге libs

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

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

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

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

Ответы [ 3 ]

9 голосов
/ 24 января 2012

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

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

[Lib]

public class A {}

[App]

public class A extends B {}

[Test]

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

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

Вы должны увидеть, что Test не компилируется, поскольку не удается разрешить A.Это ожидается, потому что Test не имеет видимости в Lib.Одним из решений является добавление ссылки на библиотеку из Test в 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, поэтому оба полученных apk включают копию com.example.A.

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

В 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 отличаются друг от друга.Экспорт проектов библиотеки из App в Eclipse не влияет на сборку Ant.Сборка не удалась, потому что тестовый проект не имеет видимости в проект Lib.Если мы попытаемся решить эту проблему, добавив свойство android.library.refernce в Test / project.properties, мы сделали то же самое, что и добавили ссылку на библиотеку из Test в Lib в Eclipse.Сборка Ant будет выполнена успешно, но во время выполнения тест завершится неудачно со знакомой ошибкой «Класс разрешен неожиданным DEX».

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

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

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

<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. Мне часто приходится делать это после внесения изменений в пути сборки. Мне также иногда приходится собирать чистые вещи, чтобы все работало правильно.

4 голосов
/ 24 января 2012

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

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

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

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

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

    <!-- override "compile" target in platform android_rules.xml to include tested app's external libraries -->
<!-- Compiles this project's .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's 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>

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

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

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

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

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

0 голосов
/ 05 апреля 2012

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

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

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

...