21

似乎最新的 android SDK 工具仍然不能正确支持包含链接库项目的应用程序的测试。

我有一个具有以下设置的项目:

TestLib (android 库项目) <- TestMain (android 项目) <- TestMainTest (android 单元测试项目)

我在 Eclipse 中创建了所有这些项目,然后用于android update (test-/lib-)project ...生成build.xmlet。人。

一旦您在 TestMain (InheritAddition.java在我的示例中)中有一个继承自 TestLib ( Addition.java) 中的类并且您想在单元测试 ( InheritAdditionTest.java) 中引用该类时,问题就开始了。

测试库

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

测试主程序

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

测试主测试

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

在命令行上构建时,结果如下:

W/ClassPathPackageInfoSource(14871):引起:java.lang.NoClassDefFoundError:org/test/main/InheritAddition
W/ClassPathPackageInfoSource(14871): ... 26 更多
W/ClassPathPackageInfoSource(14871):引起:java.lang.IllegalAccessError:预验证类中的类引用解析为意外实现
W/ClassPathPackageInfoSource(14871):在 dalvik.system.DexFile.defineClass(Native Method)
W/ClassPathPackageInfoSource(14871):在 dalvik.system.DexFile.loadClassBinaryName(DexFile.java:195)
W/ClassPathPackageInfoSource(14871):在 dalvik.system.DexPathList.findClass(DexPathList.java:315)
W/ClassPathPackageInfoSource(14871):在 dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:58)
W/ClassPathPackageInfoSource(14871):在 java.lang.ClassLoader.loadClass(ClassLoader.java:501)
W/ClassPathPackageInfoSource(14871):在 java.lang.ClassLoader.loadClass(ClassLoader.java:461)
W/ClassPathPackageInfoSource(14871): ... 26 更多
W/dalvikvm(14871):由意外 DEX 解析的类:Lorg/test/main/InheritAddition;(0x41356250):0x13772e0 ref [Lorg/test/lib/Addition;] Lorg/test/lib/Addition;(0x41356250):0x13ba910

我找到了一些适用于 eclipse 的解决方法:

当测试项目在 libs 目录中有 jar 时,无法构建和运行使用“ant create test-project”创建的 android 测试项目

这就是诀窍,但我正在寻找一种适用于 ANT 的解决方案(更准确地说,我正在寻找一种同时适用于两者的解决方案)。

记录的方法(通过更改 build.xml 以将主项目中的 jar 包含到类路径中)在此处不适用,因为示例项目不使用任何库 jar(我也相信这个特殊问题现在已通过 SDK 工具修复r16)。

我想解决这个问题的蛮力方法是尝试以某种方式删除 to 的依赖项TestMainTestTestLib通过修改project.properties),而是设法破解构建脚本以将那些构建的 jar 放入类路径(所以用-compile修改的东西替换目标的类路径javac)。由于我长期以来一直试图跟上 android SDK 工具链的变化,因此这并不是我最喜欢的选择,因为它 a) 相当复杂,b)build.xml每当工具链发生变化时(这种情况非常频繁),都需要不断地修改。

所以我正在寻找如何在不使用大锤的情况下让这样的设置工作的想法。也许我遗漏了一些非常明显的东西,但对我来说这个用例是相当标准的,我很难理解为什么不支持开箱即用。

4

3 回答 3

9

使用 Android SDK 工具 r15 和 Eclipse。

假设你在eclipse中创建了三个项目:Lib(android库项目)<-App(android应用项目)<-Test(android单元测试项目)并定义如下类:

[图书馆]

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 添加到 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 的副本。

不要在 Eclipse 中将显式依赖项从测试项目添加到库项目(如果该库是被测应用程序项目的依赖项)。这样做会导致应用程序和测试项目在其生成的 apk 中包含相同类的副本,并且测试将在运行时失败。

我们需要找到解决编译时可见性问题的方法。在运行时,Test 将对 App 以及 Lib 中的类具有可见性。不要创建从 Test 到 Lib 的库引用,而是更新 App 的构建路径以导出它的库项目。现在测试编译并且单元测试成功运行。

在 Eclipse 中,要测试引用库项目的应用程序项目,请在其构建路径设置中从应用程序项目中导出库项目。

现在一切都在 Eclipse 中工作,但是关于 Ant 呢?使用 android update [lib-|test-]project 命令创建必要的 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

为什么 Eclipse 构建成功时 Ant 构建失败?Eclipse 和 Ant 构建系统是不同的。在 Eclipse 中从 App 导出库项目对 Ant 构建没有影响。构建失败,因为 Test 项目无法看到 Lib 项目。如果我们尝试通过将 android.library.refernce 属性添加到 Test/project.properties 来解决此问题,我们所做的事情与在 Eclipse 中添加从 Test 到 Lib 的库引用完全相同。Ant 构建会成功,但测试会在运行时失败,并出现熟悉的“Class resolved by unexpected DEX”错误。

我们需要一种方法让测试项目针对库项目进行编译,但不将其包含在 dexing 过程中。这个过程有两个步骤。首先,包含一个从 Test 到 Lib 的引用,它不会影响 Eclipse。其次,更新 Ant 构建系统,以便针对该库进行编译,但将其排除在 dexing 之外。

在 Test/build.xml 的顶部,我定义了一个指向库的属性。这类似于添加对 Test/project.properties 的引用,只是 Eclipse 看不到它:

现在我们需要从 dexing 过程中排除库 jar。这需要更新 dex-helper 宏。我将宏覆盖放在我的 Test/build.xml 文件中的行之后。新的 dex-helper 从 dexing 过程中排除所有不在 Test 项目文件夹树中的 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。在进行构建路径更改后,我经常必须这样做。我有时也必须建立干净的东西才能正常工作。

于 2012-01-24T02:18:31.797 回答
4

@wallacen60 的回答很好。我昨天也得出了同样的结论。尽管如此,还有另一种选择:与其从测试项目的 dexing 中排除 lib 的 jar,不如找到一种方法将 lib 的 jar 包含在编译(javac,ant 文件的编译阶段)中测试,并且只在编译阶段而不是dexing阶段。

@wallacen60 的解决方案还引入了 3 个项目的编译和它们的依赖关系之间的一个很大的语义差异:在 Eclipse 中,App 依赖于 lib,test 依赖于 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>

事实上,这种机制似乎是正确的,因为它模仿了 eclipse 所做的事情。但是eclipse在编译测试时能够知道App依赖于lib。唯一的区别是我们在 ant 中通过该行手动公开了这个关系(在 project.properties 中)

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

但有可能自动执行此操作。我找不到 google ant 工具用于从 project.properties 中的 android.library.* 语句生成库项目路径的机制。但是如果我能找到这种机制,我就可以在测试项目中传播这种依赖关系,就像 eclipse 一样。

所以我认为最好的办法是让谷歌知道他们有补丁要做,并暂时保留手动导出应用项目对lib项目的依赖项的解决方案,以便编译测试项目。

有人可以就这个错误联系谷歌吗?

于 2012-01-24T08:04:31.110 回答
-1

这里发布的答案有点令人费解。我不确定这是否最近发生了变化,但在 r15 中,可以简单地将以下行添加到他们的 project.properties 中,一切都会构建。您无需修改​​任何构建脚本。

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

我猜它不是递归地寻找库项目,所以如果你在你正在测试的项目中引用它,你只需要在你的测试项目中引用它。

于 2012-04-05T02:31:27.353 回答