2

同僚と私は現在、レガシー Java EE5 コードベースに単体テストを導入しています。主に JUnit と Mockito を使用しています。テストを作成する過程で、EJB のいくつかのメソッドは一度に多くのことを実行するため、テストが難しいことに気付きました。

私はテストビジネス全体にかなり慣れていないので、コードまたはテストをより適切に構造化する方法についての洞察を探しています。私の目標は、頭を悩ませることなく良いテストを書くことです。

これは、メッセージ キューを管理するサービスでのメソッドとその論理ステップの 1 つの例です。

  • 消費メッセージ

    • 確認する以前にダウンロードされたメッセージ

    • getNewUnreadMessages

    • addExtraMessages (やや複雑な条件による)

    • markMessagesAsDownloaded

    • serializeMessageObjects

現在、最上位のメソッドはインターフェイスで公開されていますが、すべてのサブメソッドは非公開です。私が理解している限り、プライベート メソッドのテストを開始するのは悪い習慣です。パブリック インターフェイスだけが問題になるはずです。

私の最初の反応は、すべてのサブメソッドを公開してそれらを個別にテストすることでした。次に、トップレベルのメソッドでサブメソッドを呼び出すことを確認してください。しかし、同僚は、これらすべての低レベルのメソッドを他のメソッドと同じレベルで公開するのは良い考えではないかもしれないと述べました。混乱を引き起こし、他の開発者がトップレベルを使用する必要があるときに使用し始める可能性があるためです。 1。私は彼の主張を責めることはできない.

だからここにいます。

簡単にテストできる低レベルのメソッドを公開することと、インターフェイスが乱雑になるのを避けることをどのように調整しますか? この場合、EJB インターフェイスです。

依存性注入を使用するか、単一責任の原則に従う必要があるという他の単体テストの質問を読みましたが、実際に適用するのに問題があります。上記の例のメソッドにそのようなパターンを適用する方法について、誰かが指針を持っていますか?

他の一般的な OO パターンまたは Java EE パターンをお勧めしますか?

4

3 に答える 3

1

一見すると、おそらく新しいクラスを導入する必要があると思います。これは、1) ユニット テストが可能なパブリック メソッドを公開しますが、2) API のパブリック インターフェイスでは公開されません。

例として、自動車用の API を設計しているとします。API を実装するには、(複雑な動作をする) エンジンが必要です。エンジンを完全にテストしたいが、車の API のクライアントに詳細を公開したくない (自分の車について私が知っているのは、スタート ボタンの押し方とラジオ チャンネルの切り替え方法だけです)。

その場合、私は次のようなことをします:

public class Engine {
  public void doActionOnEngine() {}
  public void doOtherActionOnEngine() {}
}


public class Car {
  private Engine engine;

  // the setter is used for dependency injection
  public void setEngine(Engine engine) {
    this.engine = engine;
  }

  // notice that there is no getter for engine

  public void doActionOnCar() {
    engine.doActionOnEngine();
  }

  public void doOtherActionOnCar() {
    engine.doActionOnEngine();
    engine.doOtherActionOnEngine(),
  }
}

Car API を使用している人にとっては、エンジンに直接アクセスする方法がないため、害を及ぼすリスクはありません。一方、エンジンの完全な単体テストは可能です。

于 2012-10-16T07:08:16.843 に答える
1

依存性注入 (DI) と単一責任原則 (SRP) は密接に関連しています。

SRP は基本的に、各クラスは 1 つのことだけを行い、他のすべての問題を別のクラスに委任する必要があると述べています。たとえば、serializeMessageObjectsメソッドを独自のクラスに抽出する必要があります。それを と呼びましょうMessageObjectSerializer

DI は、コンストラクターまたはメソッドの呼び出しのいずれかで、オブジェクトMessageObjectSerializerを引数としてオブジェクトに注入 (渡す) ことを意味します。DI フレームワークを使用してこれを行うことができますが、概念を理解するために手動で行うことをお勧めします。MessageQueueconsumeMessages

ここで、 のインターフェースを作成するとMessageObjectSerializer、それを に渡すことができMessageQueue、簡単にテストできるようにモック/スタブを作成できるため、パターンの完全な値を取得できます。突然、振る舞いconsumeMessagesに注意を払う必要はありません。serializeMessageObjects

以下に、そのパターンを図解してみました。MessageObjectSerializerconsumerMessages をテストする場合は、オブジェクトを使用する必要がないことに注意してください。やりたいことを正確に実行するモックまたはスタブを作成し、具象クラスの代わりに渡すことができます。これにより、テストが非常に簡単になります。構文エラーを許してください。Visual Studio にアクセスできなかったので、テキスト エディターで記述します。

// THE MAIN CLASS
public class MyMessageQueue() 
{

    IMessageObjectSerializer _serializer; 



    //Constructor that takes the gets the serialization logic injected
    public MyMessageQueue(IMessageObjectSerializer serializer)
    {
        _serializer = serializer;

        //Also a lot of other injection 
    }


    //Your main method. Now it calls an external object to serialize
    public void consumeMessages()
    {
        //Do all the other stuff

        _serializer.serializeMessageObjects()
    }
}

//THE SERIALIZER CLASS
Public class MessageObjectSerializer : IMessageObjectSerializer 
{
    public List<MessageObject> serializeMessageObjects()
    {
        //DO THE SERILIZATION LOGIC HERE 
    }
}


//THE INTERFACE FOR THE SERIALIZER 
Public interface MessageObjectSerializer 
{
    List<MessageObject> serializeMessageObjects(); 
}

編集:申し訳ありませんが、私の例はC#です。とにかく使用できることを願っています:-)

于 2012-10-15T12:23:50.380 に答える
0

お気づきのとおり、具体的な高レベルのプログラムを単体テストするのは非常に困難です。また、最も一般的な 2 つの問題を特定しました。

通常、プログラムは特定のファイル、IP アドレス、ホスト名などの特定のリソースを使用するように構成されています。これに対抗するには、依存性注入を使用するようにプログラムをリファクタリングする必要があります。これは通常、ahrdcoded 値を置き換えるパラメーターをコンストラクターに追加することによって行われます。

また、大規模なクラスやメソッドをテストすることも非常に困難です。これは通常、複雑なロジックをテストするために必要なテスト数の組み合わせ爆発によるものです。これに対抗するために、通常は最初にリファクタリングしてより多くの (ただし短い) メソッドを取得し、次に元のクラスからそれぞれ単一のエントリ メソッド (パブリック) といくつかのユーティリティを持ついくつかのクラスを抽出して、コードをより汎用的でテスト可能にしようとします。メソッド (プライベート)。これは基本的に単一責任の原則です。

これで、新しいクラスをテストすることで「上へ」の作業を開始できます。この時点で組み合わせははるかに扱いやすいため、これははるかに簡単になります。

途中のある時点で、コマンド、コンポジット、アダプター、ファクトリー、ビルダー、およびファサードのデザイン パターンを使用することで、コードを大幅に簡素化できることに気付くでしょう。これらは、混乱を減らす最も一般的なパターンです。

古いプログラムのいくつかの部分は、おそらく大部分がテスト不可能になるでしょう。ここでは、既知の入力からの出力が変更されていないことを確認するだけの簡単なテストで解決できます。基本的に回帰テスト。

于 2012-10-15T12:16:12.380 に答える