簡體   English   中英

使用ant與庫項目的Android單元測試

[英]Android unit test using ant with library project

似乎最新的Android SDK工具仍然不能正確支持包含鏈接庫項目的應用程序的測試。

我有一個項目,具有以下設置:

TestLib(android庫項目)< - TestMain(android項目)< - TestMainTest(android單元測試項目)

我在eclipse中創建了所有這些項目,然后使用android update (test-/lib-)project ...來生成build.xml等。 人。

這個問題只要你有TestMain(一類開始InheritAddition.java在我的例子),從在TESTLIB(類繼承Addition.java ),並要在單元測試(以引用此類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的解決方法:

當測試的項目在libs目錄中有jar時,無法構建和運行使用“ant create test-project”創建的android測試項目

這樣做的訣竅,但我正在尋找一個適用於ANT的解決方案(更確切地說,我正在尋找一種同時適用於兩者的解決方案)。

記錄的方法(通過將build.xml更改為包括從主項目到類路徑的jar)在這里不適用,因為示例項目不使用任何庫jar(我也相信現在使用SDK工具修復了這個特定問題) R16)。

我想解決這個問題的蠻力方法是嘗試以某種方式刪除TestMainTestTestLib的依賴關系(通過修改project.properties ),而是設法破解構建腳本以將這些構建的jar放入類路徑中(因此替換-compile使用修改javac類路徑的東西-compile目標。 由於我有很長的歷史,試圖跟上android SDK工具鏈的變化,這不是我最喜歡的選項,因為它是a)相當復雜,b)需要在工具鏈發生變化時不斷修改build.xml (這是相當的經常)。

所以我正在尋找如何在不使用大錘的情況下使這種設置工作的想法。 也許我錯過了一些完全顯而易見的東西,但對我而言,這個用例是相當標准的,我很難理解為什么不支持開箱即用。

使用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庫項目,因此生成的apx都包含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

為什么在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>

通過這些更改,Test可以從Eclipse和Ant構建和運行。

快樂的測試!

其他說明:如果沒有在Eclipse中構建並且您認為它們應該存在,請嘗試按以下順序刷新項目:Lib,App,Test。 在進行構建路徑更改后,我經常需要這樣做。 我有時也必須建立干凈的東西才能正常工作。

@ wallacen60的答案很好。 我昨天得出了同樣的結論。 盡管如此,還有另外一種選擇:如果我們能找到一種方法將lib的jar包含在編譯(javac,ant文件的編譯階段)中,而不是將lib的jar從測試項目的dexing中排除,那將是很好的。測試,只在編譯階段,而不是dexing階段。

@ wallacen60的解決方案還引入了3項目及其依賴項的編譯之間的巨大語義差異:在Eclipse App中依賴於lib,測試依賴於App。 這是正確的方法。 但是在螞蟻中,App和Test都依賴於Lib,對我來說似乎是一個糟糕的冗余循環。

所以,就目前而言,我們所做的是修補測試項目的project.properties文件,使其包含以下行:

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

我們修改了測試項目的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>

實際上,這種機制似乎是正確的,因為它模仿了日食的作用。 但是eclipse能夠知道應用程序在編譯測試時依賴於lib。 唯一的區別是我們通過該行在project中手動暴露了這種關系(在project.properties中)

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

但是有可能自動完成。 我找不到google ant工具用來從project.properties中的android.library。*語句生成librairy項目路徑refid的機制。 但是,如果我能找到這種機制,我可以在測試項目中傳播這種依賴,就像eclipse那樣。

所以我認為最好的方法是讓谷歌知道他們有一個補丁要做,並暫時保留手動導出應用程序項目對lib項目的依賴關系的解決方案,以便編譯測試項目。

有人可以聯系google了解這個bug嗎?

這里發布的答案有點令人費解。 我不確定最近是否改變了但是在r15中,可以簡單地將以下行添加到他們的project.properties中,一切都將構建。 您無需修改​​任何構建腳本。

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

我猜它不是遞歸地查找庫項目,所以如果你在你正在測試的項目中引用它,你只需要在你的測試項目中引用它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM