简体   繁体   English

使用ant与库项目的Android单元测试

[英]Android unit test using ant with library project

It seems that also the latest android SDK tools still don't properly support testing of applications that contain linked library projects. 似乎最新的Android SDK工具仍然不能正确支持包含链接库项目的应用程序的测试。

I have a project with the following setup: 我有一个项目,具有以下设置:

TestLib (android library project) <- TestMain (android project) <- TestMainTest (android unit test project) TestLib(android库项目)< - TestMain(android项目)< - TestMainTest(android单元测试项目)

I created all those projects in eclipse and then used android update (test-/lib-)project ... to generate the build.xml et. 我在eclipse中创建了所有这些项目,然后使用android update (test-/lib-)project ...来生成build.xml等。 al. 人。

The problem starts as soon as you have a class in TestMain ( InheritAddition.java in my example) that inherits from a class in TestLib ( Addition.java ) and you want to reference this class in the unit test ( InheritAdditionTest.java ). 这个问题只要你有TestMain(一类开始InheritAddition.java在我的例子),从在TESTLIB(类继承Addition.java ),并要在单元测试(以引用此类InheritAdditionTest.java )。

TestLib TESTLIB

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

TestMain TestMain

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

TestMainTest TestMainTest

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

When building on the command line the result is the following: 在命令行上构建时,结果如下:

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

I found some workaround that works for eclipse: 我找到了一些适用于eclipse的解决方法:

Can't build and run an android test project created using "ant create test-project" when tested project has jars in libs directory 当测试的项目在libs目录中有jar时,无法构建和运行使用“ant create test-project”创建的android测试项目

That does the trick, but I am looking for a solution that works with ANT (more precisely I am looking for a solution that works on both at the same time). 这样做的诀窍,但我正在寻找一个适用于ANT的解决方案(更确切地说,我正在寻找一种同时适用于两者的解决方案)。

The documented approach (by changing build.xml to include jars from the main project into the class path) is not applicable here as the sample project doesn't use any library jars (also I believe that this particular problem is now fixed with SDK tools r16). 记录的方法(通过将build.xml更改为包括从主项目到类路径的jar)在这里不适用,因为示例项目不使用任何库jar(我也相信现在使用SDK工具修复了这个特定问题) R16)。

I guess the brute force way of solving that is to try and somehow remove the dependencies of TestMainTest to TestLib (by modifying project.properties ) and instead manage to hack the build script to put those built jars into the class path (so replace the -compile target with something that modifies the class path for javac ). 我想解决这个问题的蛮力方法是尝试以某种方式删除TestMainTestTestLib的依赖关系(通过修改project.properties ),而是设法破解构建脚本以将这些构建的jar放入类路径中(因此替换-compile使用修改javac类路径的东西-compile目标。 Since I have a long history of trying to keep up with android SDK toolchain changes, this is not really my favorite option as it is a) rather complicated and b) requires constant modification of the build.xml whenever the toolchain changes (which is quite frequently). 由于我有很长的历史,试图跟上android SDK工具链的变化,这不是我最喜欢的选项,因为它是a)相当复杂,b)需要在工具链发生变化时不断修改build.xml (这是相当的经常)。

So I am looking for ideas of how to get such a setup working without using the sledge hammer. 所以我正在寻找如何在不使用大锤的情况下使这种设置工作的想法。 Maybe I am missing something totally obvious but for me this use case is fairly standard and I have a hard time understanding why this isn't supported out of the box. 也许我错过了一些完全显而易见的东西,但对我而言,这个用例是相当标准的,我很难理解为什么不支持开箱即用。

Using Android SDK Tools r15 and Eclipse. 使用Android SDK工具r15和Eclipse。

Suppose you create three projects in eclipse: Lib (android library project) <- App (android application project) <- Test (android unit test project) and define the following classes: 假设你在eclipse中创建了三个项目:Lib(android库项目)< - App(android应用程序项目)< - Test(android单元测试项目)并定义以下类:

[Lib] [库]

public class A {}

[App] [应用]

public class A extends B {}

[Test] [测试]

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

In this setup TestMain references TestLib as an Android library and TestMainTest has a project reference to TestMain. 在此设置中,TestMain将TestLib引用为Android库,TestMainTest引用TestMain的项目引用。

You should see that Test doesn't compile because A cannot be resolved. 您应该看到Test无法编译,因为A无法解析。 This is expected because Test has no visibility into Lib. 这是预期的,因为Test没有对Lib的可见性。 One solution is to add a library reference from Test to Lib. 一种解决方案是从Test到Lib添加库引用。 While this fixes the compile problem, it breaks at run time. 虽然这解决了编译问题,但它在运行时中断。 A bunch of errors result, but this is the interesting one: 会产生一堆错误,但这很有趣:

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

This is because both the Test and App projects reference the Lib library project, so both resulting apks include a copy of com.example.A. 这是因为Test和App项目都引用了Lib库项目,因此生成的apx都包含com.example.A的副本。

Do not add explicit dependencies in eclipse from a test project to a library project (if that library is a dependency of the application project under test). 不要将eclipse中的显式依赖项从测试项目添加到库项目(如果该库是测试中的应用程序项目的依赖项)。 Doing so can cause both the application and test projects to include copies of the same classes in their resulting apks and the test will fail at run time. 这样做会导致应用程序和测试项目在其生成的apks中包含相同类的副本,并且测试将在运行时失败。

We need to find a way around the compile time visibility issue. 我们需要找到解决编译时可见性问题的方法。 At run time, Test will have visibility into App and therefore the classes in Lib. 在运行时,Test将可以看到App,因此可以看到Lib中的类。 Instead of creating a library reference from Test to Lib, update the build path of App to export it's library projects. 而不是从Test到Lib创建库引用,更新App的构建路径以导出它的库项目。 Now Test compiles and the unit test successfully runs. 现在测试编译并且单元测试成功运行。

In Eclipse, to test an application project that references a library project, export the library projects from the application project in it's build path settings. 在Eclipse中,要测试引用库项目的应用程序项目,请从应用程序项目的构建路径设置中导出库项目。

Everything works in Eclipse now, but that about Ant? 现在一切都在Eclipse中工作,但是关于Ant? Use the android update [lib-|test-]project commands to create the necessary build.xml files. 使用android update [lib- | test-]项目命令创建必要的build.xml文件。 Be sure to run Ant clean in all three directories: Lib, App, and Test. 确保在所有三个目录中运行Ant clean:Lib,App和Test。 Failure to clean all three projects may result in a successful compile. 无法清除所有三个项目可能会导致编译成功。

The Ant compile will fail with: 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

Why does the Ant build fail when the Eclipse build succeeds? 为什么在Eclipse构建成功时Ant构建失败了? The Eclipse and Ant build systems are distinct. Eclipse和Ant构建系统是截然不同的。 Exporting the library projects from App in Eclipse has not effect on the Ant build. 从Eclipse中的App导出库项目对Ant构建没有影响。 The build failed because the Test project doesn't have visibility into the Lib project. 构建失败,因为Test项目没有对Lib项目的可见性。 If we try to solve this problem by adding an android.library.refernce property to Test/project.properties, we have done exactly the same thing as adding a library reference from Test to Lib in Eclipse. 如果我们尝试通过向Test / project.properties添加android.library.refernce属性来解决这个问题,那么我们就完成了与在Eclipse中从Test添加库引用完全相同的事情。 The Ant build would succeed, but the test would fail at run time with the familiar “Class resolved by unexpected DEX” error. Ant构建会成功,但测试会在运行时因熟悉的“由意外DEX解析的类”错误而失败。

We need a way for the test project to compile against the library project, but not include it in the dexing process. 我们需要一种方法让测试项目针对库项目进行编译,但不要在dexing过程中包含它。 There are two steps to this process. 这个过程有两个步骤。 First, include a reference from Test to Lib that does not affect Eclipse. 首先,包括一个不影响Eclipse的Test to Lib引用。 Second, update the Ant build system so the library is compiled against but in excluded from dexing. 其次,更新Ant构建系统,以便编译库,但是从dexing中排除。

At the top of Test/build.xml I define a property that points to the library. 在Test / build.xml的顶部,我定义了一个指向库的属性。 This is similar to adding a reference to Test/project.properties except that Eclipse won't see it: 这类似于添加对Test / project.properties的引用,除了Eclipse不会看到它:

Now we need to exclude the library jar from the dexing process. 现在我们需要从dexing过程中排除库jar。 This requires updating the dex-helper macro. 这需要更新dex-helper宏。 I place the macro override after the line in my Test/build.xml file. 我将宏覆盖放在Test / build.xml文件中的行之后。 The new dex-helper excludes all jar files not in the Test project folder tree from the dexing process: 新的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>

With these changes in place, Test builds and runs from both Eclipse and Ant. 通过这些更改,Test可以从Eclipse和Ant构建和运行。

Happy testing! 快乐的测试!

Other notes: If things are not building in Eclipse and you believe they should, try refreshing the the projects in the following order: Lib, App, Test. 其他说明:如果没有在Eclipse中构建并且您认为它们应该存在,请尝试按以下顺序刷新项目:Lib,App,Test。 I frequently have to do this after making build path changes. 在进行构建路径更改后,我经常需要这样做。 I also sometimes have to build clean for things to work properly. 我有时也必须建立干净的东西才能正常工作。

The answer of @wallacen60 is nice. @ wallacen60的答案很好。 I came to the same conclusion yesterday. 我昨天得出了同样的结论。 Neverthe less, there is another option : instead of excluding the lib's jar from dexing of the test projet, it would be nice if we could find a way to include the lib's jar in the compilation (javac, compile stage of the ant file) of test, and only in the compilation stage and not the dexing stage. 尽管如此,还有另外一种选择:如果我们能找到一种方法将lib的jar包含在编译(javac,ant文件的编译阶段)中,而不是将lib的jar从测试项目的dexing中排除,那将是很好的。测试,只在编译阶段,而不是dexing阶段。

The solution of @wallacen60 moreover introduces a big semantic difference between the compilation of the 3 project and their dependencies : in Eclipse App depends on lib, test depends on App. @ wallacen60的解决方案还引入了3项目及其依赖项的编译之间的巨大语义差异:在Eclipse App中依赖于lib,测试依赖于App。 And that is the right way to do it. 这是正确的方法。 But in ant, both App and Test depend on Lib and seems like a bad redunduncy cycle to me. 但是在蚂蚁中,App和Test都依赖于Lib,对我来说似乎是一个糟糕的冗余循环。

So, for now, what we did was to patch the test project's project.properties file so that it includes this line : 所以,就目前而言,我们所做的是修补测试项目的project.properties文件,使其包含以下行:

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

And we modified the ant file of the tested project so that the compile target includes the library : (look at the changed line, search for the word "change"). 我们修改了测试项目的ant文件,以便编译目标包含库:(查看更改的行,搜索“更改”一词)。

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

Indeed, this mechanism seems to be the right one as it mimics what eclipse does. 实际上,这种机制似乎是正确的,因为它模仿了日食的作用。 But eclipse is able to know that the App depends on lib when compiling test. 但是eclipse能够知道应用程序在编译测试时依赖于lib。 The only difference is that we exposed this relation manually in ant via the line (in project.properties) 唯一的区别是我们通过该行在project中手动暴露了这种关系(在project.properties中)

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

But it could be possible to do that automatically. 但是有可能自动完成。 I can't find the mechanism that google ant tools use to produce the librairy project path refid from the android.library.* statement in a project.properties. 我找不到google ant工具用来从project.properties中的android.library。*语句生成librairy项目路径refid的机制。 But if I could find this mechanism I could propagate this dependency in the test project, as eclipse does. 但是,如果我能找到这种机制,我可以在测试项目中传播这种依赖,就像eclipse那样。

So I think the best would be to let google know that they have a patch to do, and temporarily keep the solution of exporting manually the dependency of th app project toward the lib project in order to compile the test project. 所以我认为最好的方法是让谷歌知道他们有一个补丁要做,并暂时保留手动导出应用程序项目对lib项目的依赖关系的解决方案,以便编译测试项目。

Can someone contact google about this bug ? 有人可以联系google了解这个bug吗?

The answers posted here are a little convoluted. 这里发布的答案有点令人费解。 I'm not sure if this was changed recently but in r15 one can simply add the following line to their project.properties and everything will build. 我不确定最近是否改变了但是在r15中,可以简单地将以下行添加到他们的project.properties中,一切都将构建。 You don't need to modify any of the build scripts. 您无需修改​​任何构建脚本。

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

I guess its not looking for library projects recursively, so you just have to reference it in your test project if you're referencing it in the project you're testing. 我猜它不是递归地查找库项目,所以如果你在你正在测试的项目中引用它,你只需要在你的测试项目中引用它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM