Come testare il codice non iniettabile?

14

Quindi ho la seguente parte di codice in uso su tutto il mio sistema. Al momento stiamo scrivendo i test unitari in modo retrospettivo (meglio tardi di quanto non sia mai stato il mio argomento), ma non vedo come questo sarebbe testabile?

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
        );
    }
}

Concettualmente questo dovrebbe essere applicabile a qualsiasi lingua, ma sto usando PHP. Il codice crea semplicemente un oggetto query ElasticSearch, basato su un oggetto Search , che a sua volta è costituito da un oggetto EmailAlert . Questi Search e EmailAlert sono solo POPO.

Il mio problema è che non vedo come posso prendere in giro il SearcherFactory (che usa il metodo statico), né il SearchEntityToQueryAdapter , che ha bisogno dei risultati di SearcherFactory::getSearchDirector e l'istanza Search . Come faccio a iniettare qualcosa che si costruisce dai risultati all'interno di un metodo? Forse c'è qualche schema di progettazione di cui non sono a conoscenza?

Grazie per l'aiuto!

    
posta iLikeBreakfast 20.05.2016 - 09:24
fonte

2 risposte

12

Ci sono alcune possibilità, come imitare i metodi static in PHP, la soluzione migliore che ho usato è la AspectMock libreria , che può essere tirato attraverso il compositore (come prendere in giro metodi statici è abbastanza comprensibile dalla documentazione).

Tuttavia, si tratta di una correzione dell'ultimo minuto per un problema che dovrebbe essere risolto in un modo diverso.

Se vuoi ancora testare lo strato responsabile della trasformazione delle query, c'è un modo abbastanza veloce per farlo.

Sto assumendo che in questo momento il metodo validate faccia parte di qualche classe, la soluzione molto rapida, che non richiede di trasformare tutte le chiamate statiche in chiamate di istanza, consiste nel creare classi che fungono da proxy per i metodi statici e iniettare questi proxy in classi che in precedenza utilizzavano i metodi statici.

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
            );
        }
    }
}
    
risposta data 20.05.2016 - 11:13
fonte
4

Per prima cosa, suggerirei di dividerlo in metodi separati:

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
        );
    }
}

Questo ti lascia in una situazione in cui puoi considerare di rendere pubblici questi due nuovi metodi e test unitario QueryTotal e ShowMessageWhenTotalExceedsMaximum individualmente. Un'opzione valida qui è in realtà non al test unitario QueryTotal , poiché essenzialmente testare solo ElasticSearch. Scrivere un test unitario per ShowMessageWhenTotalExceedsMaximum dovrebbe essere semplice e ha molto più senso, dal momento che verrebbe effettivamente testata la tua logica aziendale.

Se, tuttavia, preferisci testare "validate" direttamente, considera di passare la funzione di query stessa come parametro in "validate" (con un valore predefinito di $this->QueryTotal ), questo ti permetterà di prendere in giro la query funzione. Non sono sicuro di avere la sintassi PHP corretta, quindi nel caso in cui non lo facessi, leggi questo come "Pseudo codice":

public function validate($value, Constraint $constraint, $queryFunc=$this->QueryTotal)
{
    $totalCount =  $queryFunc($value);
    ShowMessageWhenTotalExceedsMaximum($totalCount,$constraint);
}
    
risposta data 20.05.2016 - 09:45
fonte

Leggi altre domande sui tag