RaB*_*RaB 21 android unit-testing android-testing
似乎最新的Android SDK工具仍然不能正确支持包含链接库项目的应用程序的测试.
我有一个项目,具有以下设置:
TestLib(android库项目)< - TestMain(android项目)< - TestMainTest(android单元测试项目)
我在eclipse中创建了所有这些项目,然后用于android update (test-/lib-)project ...生成build.xmlet.人.
一旦你在TestMain(InheritAddition.java在我的例子中)中有一个继承自TestLib(Addition.java)中的类并且你想在单元test(InheritAdditionTest.java)中引用这个类的类,问题就会开始.
public class Addition {
public int add2(int o1, int o2) {
return o1 + o2;
}
}
Run Code Online (Sandbox Code Playgroud)
public class InheritAddition extends Addition {
public int sub(int p1, int p2) {
return p1 - p2;
}
}
Run Code Online (Sandbox Code Playgroud)
public class InheritAdditionTest extends AndroidTestCase {
public void testSub() {
Assert.assertEquals(2, new InheritAddition().sub(3, 1));
}
}
Run Code Online (Sandbox Code Playgroud)
在命令行上构建时,结果如下:
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的解决方法:
当测试的项目在libs目录中有jar时,无法构建和运行使用"ant create test-project"创建的android测试项目
这样做的诀窍,但我正在寻找一个适用于ANT的解决方案(更确切地说,我正在寻找一种同时适用于两者的解决方案).
记录的方法(通过将build.xml更改为包括从主项目到类路径的jar)在这里不适用,因为示例项目不使用任何库jar(我也相信现在使用SDK工具修复了这个特定问题) R16).
我想解决了的强制方法是尝试以某种方式删除的依赖TestMainTest于TestLib(修改project.properties),而是设法破解构建脚本把那些内置罐子到类路径中(所以更换-compile的东西,修改目标类路径javac).由于我有很长的历史,试图跟上android SDK工具链的变化,这不是我最喜欢的选项,因为它是a)相当复杂,b)需要不断修改build.xml工具链的变化(这是非常频繁).
所以我正在寻找如何在不使用大锤的情况下使这种设置工作的想法.也许我错过了一些完全显而易见的东西,但对我而言,这个用例是相当标准的,我很难理解为什么开箱即用不支持.
小智 9
使用Android SDK工具r15和Eclipse.
假设你在eclipse中创建了三个项目:Lib(android库项目)< - App(android应用程序项目)< - Test(android单元测试项目)并定义以下类:
[库]
public class A {}
Run Code Online (Sandbox Code Playgroud)
[应用]
public class A extends B {}
Run Code Online (Sandbox Code Playgroud)
[测试]
public class MyUnitTest extends AndroidTestCase {
public void test() {
new A();
new B();
}
}
Run Code Online (Sandbox Code Playgroud)
在此设置中,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
Run Code Online (Sandbox Code Playgroud)
这是因为Test和App项目都引用了Lib库项目,因此两个生成的apks都包含com.example.A的副本.
不要将eclipse中的显式依赖项从测试项目添加到库项目(如果该库是测试中的应用程序项目的依赖项).这样做会导致应用程序和测试项目在其生成的apks中包含相同类的副本,并且测试将在运行时失败.
我们需要找到解决编译时可见性问题的方法.在运行时,Test将可以看到App,因此可以看到Lib中的类.而不是从Test到Lib创建库引用,更新App的构建路径以导出它的库项目.现在测试编译并且单元测试成功运行.
在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
Run Code Online (Sandbox Code Playgroud)
为什么在Eclipse构建成功时Ant构建失败了?Eclipse和Ant构建系统是截然不同的.从Eclipse中的App导出库项目对Ant构建没有影响.构建失败,因为Test项目没有对Lib项目的可见性.如果我们尝试通过向Test/project.properties添加android.library.refernce属性来解决这个问题,那么我们就完成了与在Eclipse中从Test添加库引用完全相同的事情.Ant构建会成功,但测试会在运行时因熟悉的"由意外DEX解析的类"错误而失败.
我们需要一种方法让测试项目针对库项目进行编译,但不要在dexing过程中包含它.这个过程有两个步骤.首先,包括一个不影响Eclipse的Test to Lib引用.其次,更新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>
Run Code Online (Sandbox Code Playgroud)
通过这些更改,Test可以从Eclipse和Ant构建和运行.
快乐的测试!
其他说明:如果没有在Eclipse中构建并且您认为应该这样做,请尝试按以下顺序刷新项目:Lib,App,Test.在进行构建路径更改后,我经常需要这样做.我有时也必须建立干净的东西才能正常工作.
@wallacen60 的回答很好。昨天我也得出了同样的结论。尽管如此,还有另一种选择:如果我们能找到一种方法将 lib 的 jar 包含在编译(javac,ant 文件的编译阶段)中,而不是从测试项目的 deshing 中排除 lib 的 jar,那就太好了测试,且仅在编译阶段进行,不在dexing阶段进行。
@wallacen60的解决方案还引入了3个项目的编译及其依赖项之间的巨大语义差异:在Eclipse中App依赖于lib,test依赖于App。这才是正确的做法。但在 ant 中,App 和 Test 都依赖于 Lib,对我来说似乎是一个糟糕的冗余循环。
因此,现在我们所做的是修补测试项目的 project.properties 文件,使其包含以下行:
tested.android.library.reference.1=../SDK_android
Run Code Online (Sandbox Code Playgroud)
并且我们修改了测试项目的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>
Run Code Online (Sandbox Code Playgroud)
事实上,这种机制似乎是正确的,因为它模仿了 eclipse 的作用。但是eclipse在编译test的时候是能够知道App依赖lib的。唯一的区别是我们通过行(在project.properties中)在ant中手动暴露了这个关系
tested.android.library.reference.1=../SDK_android
Run Code Online (Sandbox Code Playgroud)
但它可能会自动完成。我找不到 google ant 工具用于从 project.properties 中的 android.library.* 语句生成 librairy 项目路径 refid 的机制。但如果我能找到这种机制,我就可以在测试项目中传播这种依赖关系,就像 Eclipse 所做的那样。
所以我认为最好的办法是让谷歌知道他们有一个补丁要做,并暂时保留手动导出应用程序项目对 lib 项目的依赖关系的解决方案,以便编译测试项目。
有人可以就这个错误联系谷歌吗?
| 归档时间: |
|
| 查看次数: |
6688 次 |
| 最近记录: |