10

クラスパス内の特定のリソースを照会するために Google Reflections ライブラリを使用しています。これらのリソースは、プロジェクトのクラスと同じ場所にあります。

Eclipse で単体テストとして実行すると成功する単体テストをいくつか作成しましたが、Maven で (maven installたとえば a を使用して) 実行しようとすると、期待どおりに動作しません。いくつかのデバッグの後、明らかに問題は、Maven で実行すると、リフレクション ライブラリがリソースが配置されているクラスパス URL を見つけられないことです。

Reflections が検査する必要があるクラスパス URL をどのように決定するかを調査して、その結論に達しました。例として、次のメソッドは、Reflections が指定されたクラス ローダーで利用可能なクラスパス URL を見つける方法を示しています (元の Reflections メソッドは少し簡略化されています)。

public static Set<URL> forClassLoader(ClassLoader... classLoaders) {
    final Set<URL> result = Sets.newHashSet();
    for (ClassLoader classLoader : classLoaders) {
        while (classLoader != null) {
            if (classLoader instanceof URLClassLoader) {
                URL[] urls = ((URLClassLoader) classLoader).getURLs();
                if (urls != null) {
                    result.addAll(Sets.<URL>newHashSet(urls));
                }
            } 
            classLoader = classLoader.getParent();
        }
    }
    return result;
}

つまり、個々のクラスローダーの URL を求めて、クラスローダー階層をトラバースしています。

Eclipse では、単体テストから前のメソッドを次のように呼び出します。

    ClassLoader myClassClassLoader = <MyClass>.class.getClassLoader(); //<MyClass> is in the same classpath url than the resources I need to find
    Set<URL> urls = forClassLoader(myClassClassLoader);
    for(URL url : urls) {
      System.out.println("a url: " + url);

予想どおり、(他の多くの URL の中でも) プロジェクトの一部として構成されているクラスパス URL を確認できます。

file:<MY_PROJECT_PATH>/target/classes/
file:<MY_PROJECT_PATH>/target/test-classes/

Reflections はチャームとして機能します (Reflections が見つけるリソースは にありますfile:<MY_PROJECT_PATH>/target/classes/)。

しかし、Maven でテストを実行すると、これらの URL エントリがforClassLoaderメソッドによって返されたセットに含まれていないことに気付きました。また、残りの Reflections メソッドがこの問題に対して期待どおりに機能していません。

「驚くべき」ことは、単体テストがmavenによって実行されたときにこれを書くと:

ClassLoader myClassClassLoader = <MyClass>.class.getClassLoader();
url = myClassClassLoader.getResource("anExistingResource");
System.out.println("URL: "+url); //a valid URL

検索しようとしているリソースをクラスローダーが引き続き解決できることがわかります。Maven で実行したときforClassLoaderに、返されたプロジェクトのクラスパス URL のセットにメソッドが含まれないのはなぜでしょうか。同時に、そのような URL にあるリソースを解決することもできます (!)。

この動作の理由は何ですか? Maven によって実行される単体テストの一部として呼び出されたときに Reflections ライブラリを機能させるために試みることができる回避策はありますか?

4

5 に答える 5

7

解決しました。誰かが将来同じ問題を見つけた場合に備えて、解決策を投稿します。

プロジェクトの単体テストを実行するとき、Maven はすべての依存関係を (明示的に) クラスパスに含めません。代わりに、「target/surefire/surefirebooter_NUMBER_THAT_LOOKS_LIKE_TIME_STAMP.jar」にある tmp jar への依存関係を宣言します。この jar には、プロジェクトのクラスパスを宣言するマニフェスト ファイルのみが含まれています。

Reflections ライブラリのメソッドは、有効なforClassLoaderクラスパスを持つ一連の URL を返しません(つまり、マニフェスト ファイルのクラスパス エントリは無視されます)。これを克服するために、次の簡単な方法を実装しました。

public static Set<URL> effectiveClassPathUrls(ClassLoader... classLoaders) {
    return ClasspathHelper.forManifest(ClasspathHelper.forClassLoader(classLoaders));
}

このメソッドforManifest(これも Reflections ライブラリの一部) は、パラメータとして送信されるクラスパス URL のセットに、セットに含まれる任意の jar ファイルのマニフェスト ファイルで宣言された不足しているクラスパス エントリを追加します。このようにして、メソッドはプロジェクトの有効なクラスパスを含む一連の URL を返します。

于 2012-11-27T18:52:18.987 に答える
3

M2Eclipse を使用している可能性があります。M2Eclipse は独自にクラスパスに追加します。コマンドライン Maven の動作は異なります。役立ついくつかのオプションが見つかるかもしれません。

于 2012-11-27T03:16:43.620 に答える
2

私はまったく同じ問題を抱えていました。次の URL を追加するとうまくいきました。

ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setUrls(...);
cb.addUrls(YourClassName.class.getProtectionDomain().getCodeSource().getLocation());
于 2013-03-14T21:54:26.573 に答える
0

確実な失敗を引き起こすいくつかの問題を作成できます。

  1. 命名規則があります。テスト スイートは、'TestBlaBla' または 'BlaBlaTest' という名前にする必要があります。「テスト」という単語で始まるか終わる。

  2. 前述のように、Maven のクラスパスは Eclipse よりも制限されています。Eclipse は (愚かなことに) コンパイル クラスパスをテスト クラスパスから分離していないからです。

  3. Surefire では、さまざまなテスト スイートのテスト ケースを任意の順序で自由に実行できます。複数のテスト スイートを実行して共通ベース (メモリ内データベースや JNDI コンテキストなど) を初期化すると、テスト スイートが相互に影響を及ぼし始める競合が発生する可能性があります。テスト スイートを適切に分離するように注意する必要があります。私が使用する秘訣は、スイートごとに個別のメモリ内データベースを使用することです。また、テスト スイートごとではなく、ユニット テストごとに共有対象を初期化します。

3 はデバッグが最も難しいと言えます。Maven ではなく Eclipse で何かが機能するときはいつでも、当然、テスト スイートを分離する際に何か間違ったことをしていると思います。

于 2012-11-27T09:21:23.363 に答える