注入できないコードをテストする方法は?


13

そのため、システム全体で次のコードを使用しています。現在、ユニットテストを過去にさかのぼって記述しています(私の議論ではなかったよりも遅くなっています)が、これがどのようにテスト可能かはわかりませんか?

public function validate($value, Constraint $constraint)
{
    $searchEntity = EmailAlertToSearchAdapter::adapt($value);

    $queryBuilder = SearcherFactory::getSearchDirector($searchEntity->getKeywords());
    $adapter = new SearchEntityToQueryAdapter($queryBuilder, $searchEntity);
    $query = $adapter->setupBuilder()->build();

    $totalCount = $this->advertType->count($query);

    if ($totalCount >= self::MAXIMUM_MATCHING_ADS) {
        $this->context->addViolation(
            $constraint->message
        );
    }
}

概念的には、これはどの言語にも適用できるはずですが、私はPHPを使用しています。このコードは、オブジェクトに基づいてElasticSearchクエリオブジェクトを構築するだけで、Searchオブジェクトはオブジェクトから構築されEmailAlertます。これらSearchEmailAlert'sは単なるPOPOです。

私の問題は、SearcherFactory(静的メソッドを使用する)をモックアウトする方法、インスタンスSearchEntityToQueryAdapterからの結果を必要とするをモックアウトできないことです。メソッド内の結果から構築されたものを注入するにはどうすればよいですか?たぶん私が知らないいくつかのデザインパターンがありますか?SearcherFactory::getSearchDirector Search

助けてくれてありがとう!


@DocBrownは、$this->context->addViolation呼び出し内、内で使用されていますif
iLikeBreakfast

1
盲目だったに違いない、ごめんなさい。
Doc Brown

すべての::は静的ですか?
ユアン

はい、PHPでは::静的メソッド用です。
アンディ

@Ewanはい、::クラスの静的メソッドを呼び出します。
iLikeBreakfast

回答:


11

いくつかの可能性があります。PHPでstaticメソッドをモックする方法、私が使用した最良の解決策はAspectMockライブラリで、これはコンポーザを介してプルできます(静的メソッドをモックする方法はドキュメントから非常に理解できます)。

ただし、別の方法で修正する必要がある問題の最後の修正です。

クエリの変換を担当するレイヤーの単体テストを引き続き行う場合は、非常に簡単な方法があります。

私は今、validateメソッドがいくつかのクラスの一部であると仮定しています、非常に迅速な修正は、すべての静的呼び出しをインスタンス呼び出しに変換する必要はなく、静的メソッドのプロキシとして機能するクラスを構築し、これらのプロキシをクラスに注入することです以前は静的メソッドを使用していました。

class EmailAlertToSearchAdapterProxy
{
    public function adapt($value)
    {
        return EmailAlertToSearchAdapter::adapt($value);
    }
}

class SearcherFactoryProxy
{
    public function getSearchDirector(array $keywords)
    {
        return SearcherFactory::getSearchDirector($keywords);
    }
}

class ClassWithValidateMethod
{
    private $emailProxy;
    private $searcherProxy;

    public function __construct(
        EmailAlertToSearchAdapterProxy $emailProxy,
        SearcherFactoryProxy $searcherProxy
    )
    {
        $this->emailProxy = $emailProxy;
        $this->searcherProxy = $searcherProxy;
    }

    public function validate($value, Constraint $constraint)
    {
        $searchEntity = $this->emailProxy->adapt($value);

        $queryBuilder = $this->searcherProxy->getSearchDirector($searchEntity->getKeywords());
        $adapter = new SearchEntityToQueryAdapter($queryBuilder, $searchEntity);
        $query = $adapter->setupBuilder()->build();

        $totalCount = $this->advertType->count($query);

        if ($totalCount >= self::MAXIMUM_MATCHING_ADS) {
            $this->context->addViolation(
                $constraint->message
            );
        }
    }
}

これは完璧です!プロキシについても考えませんでした。ありがとう!
iLikeBreakfast

2
マイケル・フェザーはこれを彼の著書「レガシーコードで効果的に作業する」で「静的ラップ」テクニックと呼んでいると思います。
ラバーダック

1
@RubberDuck正直に言うと、プロキシと呼ばれるかどうかはわかりません。私はそれを使用することを覚えている限り、それが呼ばれているものです、フェザー氏の名前はおそらくより適しています、しかし、私は本を読んでいません。
アンディ

1
クラス自体は確かに「プロキシ」です。依存関係を破る手法は、「ラップ静的」IIRCと呼ばれます。この本を強くお勧めします。あなたがここで提供したような宝石がいっぱいです。
ラバーダック

5
ジョブにコードへの単体テストの追加が含まれる場合は、「レガシーコードの使用」を強くお勧めします。彼の「レガシーコード」の定義は「単体テストのないコード」であり、本全体は、実際に単体テストを既存の未テストコードに追加するための戦略です。
Eterm

4

まず、これを別々のメソッドに分割することをお勧めします。

public function validate($value, Constraint $constraint)
{
    $totalCount = QueryTotal($value);
    ShowMessageWhenTotalExceedsMaximum($totalCount,$constraint);
}

private function QueryTotal($value)
{
    $searchEntity = EmailAlertToSearchAdapter::adapt($value);

    $queryBuilder = SearcherFactory::getSearchDirector($searchEntity->getKeywords());
    $adapter = new SearchEntityToQueryAdapter($queryBuilder, $searchEntity);
    $query = $adapter->setupBuilder()->build();

    return $this->advertType->count($query);
}

private function ShowMessageWhenTotalExceedsMaximum($totalCount,$constraint)
{
    if ($totalCount >= self::MAXIMUM_MATCHING_ADS) {
        $this->context->addViolation(
            $constraint->message
        );
    }
}

これにより、これらの2つの新しいメソッドを公開し、ユニットテストQueryTotalShowMessageWhenTotalExceedsMaximum個別に行うことを検討できる状況になります。ここで実行可能なオプションは、基本的にElasticSearchのみをテストするため、実際に単体テストではありませんQueryTotal。単体テストを書くのはShowMessageWhenTotalExceedsMaximum簡単で、はるかに理にかなっているはずです。実際にビジネスロジックをテストするからです。

ただし、「検証」を直接テストする場合は、クエリ関数自体をパラメーターとして「検証」(デフォルト値$this->QueryTotal)に渡すことを検討してください。これにより、クエリ関数をモックアウトできます。PHPの構文が正しいかどうかはわかりません。もしそうでない場合は、「擬似コード」と読んでください。

public function validate($value, Constraint $constraint, $queryFunc=$this->QueryTotal)
{
    $totalCount =  $queryFunc($value);
    ShowMessageWhenTotalExceedsMaximum($totalCount,$constraint);
}

私はこのアイデアが好きですが、このようなメソッドを渡すのではなく、コードをよりオブジェクト指向に保ちたいです。
iLikeBreakfast

@iLikeBreakfastは実際、このアプローチは他に関係なく優れています。メソッドはできるだけ短くし、1つのことと1つのことをうまくやる必要があります(Uncle Bob、Clean Code)。これにより、読みやすく、理解しやすく、テストしやすくなります。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.