使用 ant 和库项目进行 Android 单元测试 [英] Android unit test using ant with library project

查看:50
本文介绍了使用 ant 和库项目进行 Android 单元测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

只要您在 TestMain 中有一个类(在我的示例中为 InheritAddition.java)继承自 TestLib 中的类(Addition.java)和您想在单元测试中引用这个类 (InheritAdditionTest.java).

测试库

公共类加法{公共 int add2(int o1, int o2) {返回 o1 + o2;}}

测试主

public class InheritAddition extends Addition {公共 int sub(int p1, int p2) {返回 p1 - p2;}}

TestMainTest

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

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

<前>W/ClassPathPackageInfoSource(14871):引起:java.lang.NoClassDefFoundError:org/test/main/InheritAdditionW/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):0x13

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

可以'不构建和运行使用ant create test-project"创建的android测试项目;当测试项目在 libs 目录中有 jars 时

这确实有用,但我正在寻找一种适用于 ANT 的解决方案(更准确地说,我正在寻找一种同时适用于两者的解决方案).

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

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

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

解决方案

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

@wallacen60 的解决方案还引入了 3 个项目的编译和它们的依赖之间的一个很大的语义差异:在 Eclipse 中 App 依赖于 lib,测试依赖于 App.这是正确的做法.但是在 ant 中,App 和 Test 都依赖于 Lib,对我来说似乎是一个糟糕的冗余循环.

所以,现在,我们所做的是修补测试项目的 project.properties 文件,使其包含以下行:

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

我们修改了测试项目的ant文件,使编译目标包含库:(查看更改的行,搜索更改"一词).

 <!-- 覆盖平台 android_rules.xml 中的编译"目标以包含测试应用程序的外部库 --><!-- 将此项目的 .java 文件编译为 .class 文件.--><目标名称="-compile"depends="-build-setup,-pre-build,-code-gen,-pre-compile"><do-only-if-manifest-hasCode elseText="hasCode = false.跳过..."><!-- 如果android 规则用于测试项目,则其类路径应包括测试项目的位置 --><condition property="extensible.classpath"value="${tested.project.absolute.dir}/bin/classes"其他=."><isset property="tested.project.absolute.dir"/></条件><condition property="extensible.libs.classpath"value="${tested.project.absolute.dir}/${jar.libs.dir}"else="${jar.libs.dir}"><isset property="tested.project.absolute.dir"/></条件><echo message="jar libs 目录:${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"详细="${详细}"classpath="${extensible.classpath}"classpathref="jar.libs.ref"><src path="${source.absolute.dir}"/><src path="${gen.absolute.dir}"/><类路径><!-- steff:我们在这里更改了一行!--><fileset dir="${tested.android.library.reference.1}/bin/" includes="*.jar"/><fileset dir="${extensible.libs.classpath}" includes="*.jar"/></classpath><compilerarg line="${java.compilerargs}"/></javac><!-- 如果项目已检测,则检测类--><if condition="${build.is.instrumented}"><然后><echo>检测来自 ${out.absolute.dir}/classes...</echo> 的类<!-- 它只检测类文件,而不检测任何外部库 --><instr verbosity="${verbosity}"模式=覆盖"instrpath="${out.absolute.dir}/classes"outdir="${out.absolute.dir}/classes"></instr><!-- TODO:R*.class 上的排除过滤器并允许自定义排除用户定义文件 --></艾玛></然后></如果></do-only-if-manifest-hasCode></目标>

确实,这种机制似乎是正确的,因为它模仿了 eclipse 的作用.但是eclipse在编译test的时候可以知道这个App依赖于lib.唯一的区别是我们通过行(在 project.properties)

在 ant 中手动暴露了这个关系

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

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

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

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

It seems that also the latest android SDK tools still don't properly support testing of applications that contain linked library projects.

I have a project with the following setup:

TestLib (android library project) <- TestMain (android project) <- TestMainTest (android unit test project)

I created all those projects in eclipse and then used android update (test-/lib-)project ... to generate the build.xml et. 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).

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));
    }
}

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:

Can't build and run an android test project created using "ant create test-project" when tested project has jars in libs directory

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).

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).

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). 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).

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.

解决方案

The answer of @wallacen60 is nice. 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.

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

So, for now, what we did was to patch the test project's project.properties file so that it includes this line :

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").

    <!-- 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. The only difference is that we exposed this relation manually in ant via the line (in 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. But if I could find this mechanism I could propagate this dependency in the test project, as eclipse does.

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.

Can someone contact google about this bug ?

这篇关于使用 ant 和库项目进行 Android 单元测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆