13

質問

複雑な検証ロジックが必要なオブジェクトグラフの構築をどのように管理すればよいでしょうか。テスト容易性の理由から、依存性注入された何もしないコンストラクターを保持したいと思います。

テスト容易性は私にとって非常に重要ですが、コードのこの属性を維持するためにあなたの提案は何をしますか?

バックグラウンド

いくつかのビジネスデータの構造を管理するplain-old-java-objectがあります。

class Pojo
{
    protected final String p;

    public Pojo(String p) {
        this.p = p;
    }
}

このビジネスオブジェクトはその保証なしでは本当に意味がないので、pが有効な形式であることを確認したいと思います。pがナンセンスである場合でも、作成されるべきではありません。ただし、pの検証は自明ではありません。

キャッチ

実際には、ロジック自体が完全にテスト可能であるために十分に複雑な検証ロジックが必要であるため、そのロジックは別のクラスにあります。

final class BusinessLogic() implements Validator<String>
{
    public String validate(String p) throws InvalidFoo, InvalidBar {
        ...
    }
}

重複する可能性のある質問

  • 検証ロジックはどこに実装する必要がありますか?-受け入れられた答えは私には突き通せません。「クラスのネイティブ環境で実行する」をトートロジーとして読みましたが、「クラスのネイティブ環境」以外で検証ルールを実行するにはどうすればよいですか?ポイント2グロクに失敗したので、コメントできません。
  • 検証用のロジックルールをどこに提供しますか?-どちらの回答も、責任はクライアント/データプロバイダーにあることを示唆しています。これは原則として私が好きです。ただし、私の場合、クライアントはデータの発信者ではなく、データを検証できない可能性があります。
  • 検証ロジックをどこに保持しますか?-検証はモデルが所有できることを示唆していますが、このアプローチはテストには理想的ではないと思います。具体的には、すべての単体テストで、モデルの他の部分をテストしているときでも、すべての検証ロジックに注意を払う必要があります。提案されたアプローチに従って、テストしたいものを完全に分離することはできません。

これまでの私の考え

次のコンストラクターはPojoの依存関係を公然と宣言し、その単純なテスト容易性を維持していますが、完全に安全ではありません。すべての入力が受け入れ可能であると主張するバリデーターをクライアントが提供することを妨げるものは何もありません!

public Pojo(Validator businessLogic, String p) throws InvalidFoo, InvalidBar {
    this.p = businessLogic.validate(p);
}

したがって、コンストラクターの可視性をいくらか制限し、検証と構築を保証するファクトリメソッドを提供します。

@VisibleForTesting
Pojo(String p) {
    this.p = p;
}

public static Pojo createPojo(String p) throws InvalidFoo, InvalidBar {
    Validator businessLogic = new BusinessLogic();
    businessLogic.validate(p);
    return new Pojo(p);
}

これで、createPojoをファクトリクラスにリファクタリングできます。これにより、Pojoに「単一責任」が復元され、ファクトリロジックのテストが簡素化されます。もちろん、新しいPojoごとに新しい(ステートレス)BusinessLogicを無駄に作成しないというパフォーマンス上の利点もあります。

私の直感は、私はやめるべきだということです。外部の意見を求めてください。私は正しい方向に進んでいますか?

4

3 に答える 3

13

以下の回答のいくつかの要素...それが理にかなっているかどうかを教えてください/あなたの質問に答えてください。


はじめに:システムは、単純なライブラリ、多層アプリケーション、または複雑な分散システムのいずれかであると思いますが、検証に関しては実際にはそれほど違いはありません。

  • クライアント:リモートクライアント(HTTPクライアントなど)またはライブラリを呼び出す別のクラス。
  • サービス:リモートサービス(RESTサービスなど)またはAPIを公開するもの。

どこで検証しますか?

通常、入力パラメータを検証する必要があります。

  • クライアント側では、パラメータをサービスに渡す前に、オブジェクトが将来的に有効になることを早期に確認します。これは、リモートサービスの場合、またはパラメーターの生成とオブジェクトの作成の間に複雑なフローがある場合に特に望ましいです。

  • サービス側:

    • クラスレベルで、コンストラクターで、有効なオブジェクトを確実に作成するため。
    • サブシステムレベル、つまりこれらのオブジェクトを管理するレイヤー(たとえば、DALがを永続化するPojo)。
    • サービスの境界、たとえば、ライブラリまたはコントローラーのファサードまたは外部API(MVCの場合、たとえば、RESTエンドポイント、Springコントローラーなど)。

検証する方法は?

上記を想定すると、検証ロジックを複数の場所で再利用する必要がある場合があるため、ユーティリティクラスで抽出することをお勧めします。こちらです:

  • あなたはそれを複製しません(DRY!);
  • システムのすべてのレイヤーが同じ方法で検証されることを確信しています。
  • このロジックは個別に簡単に単体テストできます(ステートレスであるため)。

より具体的には、少なくともコンストラクターでこのロジックを呼び出して、オブジェクトの有効性を強制します(のメソッドに存在するアルゴリズムの前提条件として、有効な依存関係があると考えてください)。Pojo

ユーティリティクラス

public final class PojoValidator() {
    private PojoValidator() {
        // Pure utility class, do NOT instantiate.
    }

    public static String validateFoo(final String foo) {
        // Validate the provided foo here.
        // Validation logic can throw:
        // - checked exceptions if you can/want to recover from an invalid foo, 
        // - unchecked exceptions if you consider these as runtime exceptions and can't really recover (less clutering of your API).
        return foo;
    }

    public static Bar validateBar(final Bar bar) {
        // Same as above...
        // Can itself call other validators.
        return bar;
    }
}

Pojoクラス:読みやすくするための静的インポートステートメントに注意してください。

import static PojoValidator.validateFoo;
import static PojoValidator.validateBar;

class Pojo {
    private final String foo;
    private final Bar bar;

    public Pojo(final String foo, final Bar bar) {
        validateFoo(foo);
        validateBar(bar);
        this.foo = foo;
        this.bar = bar;
    }
}

Pojoをテストする方法は?

  1. 作成ユニットテストを追加して、構築時に検証ロジックが呼び出されるようにする必要があります(X、Y、Zの理由でこの検証ロジックを削除することで、後でコンストラクターを「単純化」するためのリグレッションを回避します)。

  2. 使用するものはすべてローカルであるため(スクロールが少ない、精神的なフットプリントが小さいなど)、テストが読みやすくなるため、依存関係が単純な場合はインラインで作成します。

  3. ただし、Pojoの依存関係の設定が複雑で複雑で、テストが読み取れなくなった場合は、この設定を@Before/setUpメソッドで除外して、単体テストのテストPojoのロジックが実際にテストの動作の検証に集中するようにすることができます。ポジョ。

いずれにせよ、私はジェフ・ストーリーに同意します:

  1. 有効なパラメータを使用してテストを記述し、
  2. テストのためだけに検証なしのコンストラクターを持たないでください。実は、本番コードとテストコードを組み合わせるとコードの臭いになり、サービスの安定性を構成する、ある時点で誰かが誤って使用することは間違いありません。

最後に、テストをコードサンプル、例、または実行可能仕様と考えてください。紛らわしい例を次のように示したくありません。

  • 無効なパラメータを挿入します。
  • バリデーターを挿入すると、APIが乱雑になり、「奇妙な」と表示されます。

Pojo非常に複雑な依存関係が必要な場合はどうなりますか?

[その場合はお知らせください]

プロダクションコード:この複雑さを工場で隠そうとすることができます。

テストコード:いずれか:

  • 上記のように、これを別の方法で除外します。また
  • 工場を使用してください。また
  • モックパラメーターを使用し、テストに合格するための要件を検証するように構成します。

編集:セキュリティのコンテキストでの入力検証に関するいくつかのリンク。これも役立ちます。

于 2013-03-11T12:01:32.363 に答える
3

まず、検証ロジックはクライアントだけに存在するべきではないと言います。これは、データストアに無効なデータを入れてしまうことがないようにするのに役立ちます。クライアントを追加する場合(たとえば、シッククライアントがあり、Webサービスクライアントを追加する場合は、両方の場所で検証を維持する必要があります)。

@VisibleForTesting(そのアノテーションを使用して)検証されないオブジェクトを構築するためのコンストラクターが必要だとは思いません。通常、有効なオブジェクトを使用してテストする必要があります(エラーケースをテストする場合を除く)。また、テスト専用のコードを本番コードに追加することは、実際には本番コードではないため、コードの臭いです。

検証ロジックを配置する適切な場所は、ドメインオブジェクト自体の中にあると思います。これにより、無効なドメインオブジェクトを作成しないようになります。

バリデーターをドメインオブジェクトに渡すというアイデアはあまり好きではありません。これは、バリデーターについて知る必要があるドメインオブジェクトのクライアントに多くの作業を課します。別のバリデーターを作成したい場合は、再利用とモジュール化のメリットが追加される可能性がありますが、私はそれを注入しません。テストでは、検証を完全にバイパスするモックオブジェクトをいつでも使用できます。

ドメインモデルに検証を追加することは、Webフレームワーク(grails / rails)が行う一般的なことです。これは良いアプローチだと思います。テストのしやすさを損なうことはないはずです。

于 2013-03-10T22:25:37.357 に答える
3

pこのビジネスオブジェクトはその保証なしでは本当に意味がないので、それが有効な形式であることを確認したいと思います。pナンセンスな場合でも作成しないでください。ただし、検証pは簡単ではありません。

  1. p有効な形式の」とは、のタイプがp単なるではなくString、より具体的なタイプであることを意味します。

EmailString

class Pojo
{
    protected final EmailString p;

    public Pojo(EmailString p) {
        this.p = p;
    }
}
  1. Stringとの間にコンバータが必要EmailStringです。このコンバーターは、ビジネスロジックの一部であり、p無効にならないようにします。String消費者が作成したいときはいつでもPojo、このコンバーターを使用します。

このコンバーターを実装して変換不可能なケースを便利にキャッチする方法には、いくつかのバリエーションがあります。たとえば、.netスタイルは次のとおりです。

class Converter
{
    public static bool TryParse(String s, out EmailString p) {

    }
}

テスト容易性は私にとって非常に重要ですが、コードのこの属性を維持するためにあなたの提案は何をしますか?

このようにして、ロジックの解析/変換Pojoとは別にロジックをテストできます。String

于 2015-05-25T13:04:10.480 に答える