21

また、最新の Android SDK ツールは、リンクされたライブラリ プロジェクトを含むアプリケーションのテストをまだ適切にサポートしていないようです。

次の設定のプロジェクトがあります。

TestLib (android ライブラリ プロジェクト) ← TestMain (android プロジェクト) ← TestMainTest (android 単体テスト プロジェクト)

これらすべてのプロジェクトを Eclipse で作成し、 etandroid 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): 原因: java.lang.NoClassDefFoundError: org/test/main/InheritAddition
W/ClassPathPackageInfoSource(14871): ... 26 件以上
W/ClassPathPackageInfoSource(14871): 原因: java.lang.IllegalAccessError: 検証済みクラスのクラス参照が予期しない実装に解決されました
W/ClassPathPackageInfoSource(14871): dalvik.system.DexFile.defineClass(ネイティブ メソッド) で
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

私は日食のために働くいくつかの回避策を見つけました:

テストされたプロジェクトの libs ディレクトリに jar がある場合、「ant create test-project」を使用して作成された Android テスト プロジェクトをビルドして実行することはできません

これでうまくいきますが、ANT で動作するソリューションを探しています (より正確には、両方で同時に動作するソリューションを探しています)。

サンプル プロジェクトはライブラリ jar を使用しないため、文書化されたアプローチ (build.xml を変更してメイン プロジェクトの 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 で 3 つのプロジェクトを作成するとします。 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 は Android ライブラリとして TestLib を参照し、TestMainTest は TestMain へのプロジェクト参照を持っています。

A を解決できないため、 Test がコンパイルされないことがわかります。Test には Lib への可視性がないため、これは予期されることです。1 つの解決策は、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 へのライブラリ参照を作成する代わりに、アプリのビルド パスを更新して、そのライブラリ プロジェクトをエクスポートします。テストがコンパイルされ、単体テストが正常に実行されるようになりました。

Eclipse で、ライブラリ プロジェクトを参照するアプリケーション プロジェクトをテストするには、ビルド パス設定でアプリケーション プロジェクトからライブラリ プロジェクトをエクスポートします。

今ではすべてが Eclipse で動作しますが、それは Ant についてですか? android update [lib-|test-]project コマンドを使用して、必要な build.xml ファイルを作成します。Lib、App、Test の 3 つのディレクトリすべてで Ant clean を実行してください。3 つのプロジェクトすべてを消去しないと、コンパイルが成功する可能性があります。

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 のビルド システムは異なります。App in Eclipse からライブラリ プロジェクトをエクスポートしても、Ant ビルドには影響しません。Test プロジェクトには Lib プロジェクトへの可視性がないため、ビルドは失敗しました。android.library.refernce プロパティを Test/project.properties に追加してこの問題を解決しようとすると、Eclipse で Test から Lib にライブラリ参照を追加するのとまったく同じことを行ったことになります。Ant のビルドは成功しますが、テストは実行時におなじみの「クラスが予期しない DEX によって解決されました」というエラーで失敗します。

テスト プロジェクトをライブラリ プロジェクトに対してコンパイルする方法が必要ですが、dexing プロセスには含めません。このプロセスには 2 つのステップがあります。最初に、Eclipse に影響を与えない Test から Lib への参照を含めます。次に、Ant ビルド システムを更新して、ライブラリがコンパイルされ、dexing から除外されるようにします。

Test/build.xml の先頭で、ライブラリを指すプロパティを定義します。これは、Eclipse が参照しない点を除いて、Test/project.properties への参照を追加するのと似ています。

ここで、ライブラリ jar を dexing プロセスから除外する必要があります。これには、dex-helper マクロを更新する必要があります。Test/build.xml ファイルの行の後にマクロ オーバーライドを配置します。新しい dex-helper は、テスト プロジェクト フォルダー ツリーにないすべての jar ファイルを dexing プロセスから除外します。

<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 の答えは素晴らしいです。私も昨日同じ結論に達しました。それにもかかわらず、別のオプションがあります。lib の jar をテスト プロジェクトの dexing から除外する代わりに、lib の jar をコンパイル (javac、ant ファイルのコンパイル段階) に含める方法を見つけることができればよいでしょう。テスト、およびコンパイル段階でのみであり、dexing 段階ではありません。

さらに、@ wallacen60 のソリューションは、3 つのプロジェクトのコンパイルとその依存関係の間に大きな意味上の違いをもたらします。Eclipse では、アプリは lib に依存し、テストは 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 はテストをコンパイルするときに、アプリが lib に依存していることを知ることができます。唯一の違いは、(project.properties 内の) 行を介して ant でこの関係を手動で公開したことです。

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

しかし、それを自動的に行うことは可能です。project.properties の android.library.* ステートメントからライブラリ プロジェクト パス refid を生成するために Google ant ツールが使用するメカニズムが見つかりません。しかし、このメカニズムを見つけることができれば、Eclipse が行うように、テスト プロジェクトでこの依存関係を伝播することができます。

したがって、テスト プロジェクトをコンパイルするために、アプリ プロジェクトの依存関係を lib プロジェクトに手動でエクスポートするソリューションを一時的に保持することをお勧めします。

このバグについて Google に連絡できますか?

于 2012-01-24T08:04:31.110 に答える
-1

ここに投稿された回答は少し複雑です。これが最近変更されたかどうかはわかりませんが、r15 では、次の行を project.properties に追加するだけですべてがビルドされます。ビルド スクリプトを変更する必要はありません。

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

ライブラリプロジェクトを再帰的に探しているわけではないので、テストしているプロジェクトで参照している場合は、テストプロジェクトで参照する必要があります。

于 2012-04-05T02:31:27.353 に答える