6

ご協力ありがとうございました。私のアプローチ全体が間違っていたこと、または低レベルのコードがコンテナーで実行されているかどうかを知る必要がないことを示す回答を (私が予想していたはずですが) 投稿した多くの人がいます。私は同意する傾向があります。しかし、私は複雑なレガシー アプリケーションを扱っており、現在の問題に対して主要なリファクタリングを行うオプションはありません。

一歩下がって、元の質問の動機となった質問をしてみましょう。

JBoss で実行しているレガシー アプリケーションがあり、下位レベルのコードにいくつかの変更を加えました。変更の単体テストを作成しました。テストを実行するには、データベースに接続する必要があります。

レガシ コードは、次の方法でデータ ソースを取得します。

(jndiName は定義済みの文字列です)

Context ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup(jndiName);

私の問題は、単体テストでこのコードを実行すると、Context にデータ ソースが定義されていないことです。これに対する私の解決策は、アプリケーション サーバーで実行されているかどうかを確認し、そうでない場合は、テスト用の DataSource を作成して返すことでした。アプリ サーバーで実行している場合は、上記のコードを使用します。

だから、私の本当の質問は次のとおりです。これを行う正しい方法は何ですか? テスト対象のコードが実行されている場所を認識する必要がないように、適切なデータ ソースを返すように単体テストでコンテキストを設定できる承認された方法はありますか?


コンテキストについて: 私の元の質問:

JBoss で実行されているかどうかを知る必要がある Java コードがいくつかあります。コードがコンテナー内で実行されているかどうかを確認する標準的な方法はありますか?

私の最初のアプローチは、実験を通じて開発されたもので、初期コンテキストを取得し、特定の値を検索できることをテストすることで構成されています。

private boolean isRunningUnderJBoss(Context ctx) {
        boolean runningUnderJBoss = false;
        try {
            // The following invokes a naming exception when not running under
            // JBoss.
            ctx.getNameInNamespace();

            // The URL packages must contain the string "jboss".
            String urlPackages = (String) ctx.lookup("java.naming.factory.url.pkgs");
            if ((urlPackages != null) && (urlPackages.toUpperCase().contains("JBOSS"))) {
                runningUnderJBoss = true;
            }
        } catch (Exception e) {
            // If we get there, we are not under JBoss
            runningUnderJBoss = false;
        }
        return runningUnderJBoss;
    }

Context ctx = new InitialContext();
if (isRunningUnderJboss(ctx)
{
.........

さて、これは機能しているように見えますが、ハックのように感じます. これを行う「正しい」方法は何ですか?理想的には、JBoss だけでなく、さまざまなアプリケーション サーバーで動作する方法が必要です。

4

7 に答える 7

5

全体のコンセプトは前後にある。下位レベルのコードは、この種のテストを行うべきではありません。別の実装が必要な場合は、関連する時点でそれを伝えてください。

于 2009-05-06T21:25:10.387 に答える
4

通常、依存性注入 (Spring、構成ファイル、またはプログラム引数によるもの) と Factory パターンの組み合わせが最適です。

例として、耳または戦争が開発、テスト、または実稼働環境に入るかどうかに応じて構成ファイルをセットアップする Ant スクリプトに引数を渡します。

于 2009-05-06T21:56:48.970 に答える
2

全体のアプローチは私に向かって間違っていると感じています。アプリがどのコンテナで実行されているかを知る必要がある場合は、何か問題があります。

Springを使用すると、TomcatからWebLogicに移動したり、何も変更せずに元に戻したりできます。適切な設定で、JBOSSでも同じトリックを実行できると確信しています。それが私が狙う目標です。

于 2009-05-06T21:41:03.080 に答える
1

おそらくこのようなものです(醜いですが、うまくいくかもしれません)

 private void isRunningOn( String thatServerName ) { 

     String uniqueClassName = getSpecialClassNameFor( thatServerName );
     try { 
         Class.forName( uniqueClassName );
     } catch ( ClassNotFoudException cnfe ) { 
         return false;
     }
     return true;
 } 

getSpecialClassNameForメソッドは、アプリケーション サーバーごとに一意のクラスを返します (さらにアプリ サーバーが追加されると、新しいクラス名が返される場合があります)。

次に、次のように使用します。

  if( isRunningOn("JBoss")) {
         createJBossStrategy....etcetc
  }
于 2009-05-06T21:18:56.830 に答える
1
Context ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup(jndiName);

InitialContext を構築するのは誰ですか? その構築は、テストしようとしているコードの外にある必要があります。そうしないと、コンテキストをモックできません。

レガシー アプリケーションで作業しているとのことなので、まずコードをリファクタリングして、コンテキストまたはデータ ソースをクラスに簡単に依存性注入できるようにします。そうすれば、そのクラスのテストをより簡単に作成できます。

クラスを構築するコードをリファクタリングするまで、以下のコードのように 2 つのコンストラクターを使用することで、レガシー コードを移行できます。このようにして、Foo をより簡単にテストでき、Foo を使用するコードを変更せずに保つことができます。次に、コードをゆっくりとリファクタリングして、古いコンストラクターを完全に削除し、すべての依存関係を依存関係に注入することができます。

public class Foo {
  private final DataSource dataSource;
  public Foo() { // production code calls this - no changes needed to callers
    Context ctx = new InitialContext();
    this.dataSource = (DataSource) ctx.lookup(jndiName);
  }
  public Foo(DataSource dataSource) { // test code calls this
    this.dataSource = dataSource;
  }
  // methods that use dataSource
}

しかし、そのリファクタリングを開始する前に、いくつかの統合テストを行う必要があります。そうしないと、DataSource ルックアップをコンストラクターに移動するなどの単純なリファクタリングでさえ、何かが壊れるかどうかを知ることができません。その後、コードが改善され、テストしやすくなったら、単体テストを作成できます。(定義上、テストがファイル システム、ネットワーク、またはデータベースに触れる場合、それは単体テストではなく、統合テストです。)

単体テストの利点は、1 秒あたり数百または数千の速度で実行され、一度に 1 つの動作のみをテストすることに重点を置いていることです。これにより、頻繁に実行できるようになり (1 行を変更した後にすべての単体テストを実行することをためらうと、実行速度が遅すぎます)、迅速なフィードバックが得られます。また、それらは非常に焦点が絞られているため、失敗したテストの名前を見るだけで、本番コードの正確な場所にバグがあることがわかります。

統合テストの利点は、すべてのパーツが正しく接続されていることを確認できることです。これも重要ですが、データベースに触れるなどの理由で非常に遅くなるため、あまり頻繁に実行することはできません。ただし、継続的インテグレーション サーバーで少なくとも 1 日に 1 回は実行する必要があります。

于 2009-05-06T22:44:10.900 に答える
1

この問題に対処するには、いくつかの方法があります。1 つは、単体テスト中に Context オブジェクトをクラスに渡すことです。メソッド シグネチャを変更できない場合は、初期コンテキストの作成を保護されたメソッドにリファクタリングし、メソッドをオーバーライドして、モックされたコンテキスト オブジェクトを返すサブクラスをテストします。これにより、少なくともクラスをテストできるので、そこからより良い代替案にリファクタリングできます。

次のオプションは、データベース接続をコンテナ内にあるかどうかを判断できるファクトリにし、それぞれの場合に適切な処理を行うことです。

考えるべきことの 1 つは、コンテナーからこのデータベース接続を取得したら、それをどうするかということです。簡単ですが、データ アクセス レイヤー全体を実行する必要がある場合は、単体テストとは言えません。

単体テストの下でレガシー コードを移行するこの方向のさらなるヘルプについては、Michael Feather のWorking Effectively with Legacy Codeを参照することをお勧めします。

于 2009-05-06T21:21:58.383 に答える
-1

これを行うクリーンな方法は、ライフサイクル リスナーを で構成することweb.xmlです。これらは、必要に応じてグローバル フラグを設定できます。たとえば、メソッドでServletContextListenerを定義し、コンテナー内で実行しているグローバル フラグを設定できます。グローバル フラグが設定されていない場合は、コンテナー内で実行されていません。web.xmlcontextInitialized

于 2009-05-06T21:19:25.503 に答える