¿Cómo puedo probar el código no inyectable?

14

Tengo el siguiente código en uso en todo mi sistema. Actualmente estamos escribiendo las pruebas unitarias de forma retrospectiva (mejor tarde que nunca fue mi argumento), pero no veo cómo esto sería verificable.

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

Conceptualmente, esto debería ser aplicable a cualquier idioma, pero estoy usando PHP. El código simplemente construye un objeto de consulta ElasticSearch, basado en un objeto Search , que a su vez está construido a partir de un objeto EmailAlert . Estos Search y EmailAlert 's son solo POPO's.

Mi problema es que no veo cómo puedo simular el SearcherFactory (que utiliza el método estático), ni el SearchEntityToQueryAdapter , que necesita los resultados de SearcherFactory::getSearchDirector y la instancia Search . ¿Cómo inyecto algo que se construye a partir de resultados dentro de un método? Tal vez hay algún patrón de diseño que no conozco?

¡Gracias por cualquier ayuda!

    
pregunta iLikeBreakfast 20.05.2016 - 09:24

2 respuestas

12

Hay algunas posibilidades, cómo simular métodos de static en PHP, la mejor solución que he usado es la biblioteca AspectMock , que se puede extraer a través del compositor (la forma de simular métodos estáticos es bastante comprensible en la documentación).

Sin embargo, es una solución de último momento para un problema que debería solucionarse de otra manera.

Si aún desea realizar una prueba unitaria de la capa responsable de transformar las consultas, hay una forma bastante rápida de hacerlo.

Supongo que en este momento el método validate es parte de alguna clase, la solución muy rápida, que no requiere que transformes todas tus llamadas estáticas a llamadas de instancia, es crear clases que actúen como proxies para tus métodos estáticos e inyecte estos proxies en las clases que anteriormente utilizaban los métodos estáticos.

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
            );
        }
    }
}
    
respondido por el Andy 20.05.2016 - 11:13
4

Primero, sugeriría dividir esto en métodos separados:

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

Esto te deja en una situación en la que puedes considerar hacer que esos dos nuevos métodos sean públicos y unit unit test QueryTotal y ShowMessageWhenTotalExceedsMaximum individualmente. Una opción viable aquí es en realidad no a la prueba unitaria QueryTotal , ya que esencialmente solo probarías en ElasticSearch. Escribir una prueba de unidad para ShowMessageWhenTotalExceedsMaximum debería ser fácil y tiene mucho más sentido, ya que en realidad probaría la lógica de su negocio.

Sin embargo, si prefiere probar "validar" directamente, considere pasar la función de consulta como un parámetro a "validar" (con un valor predeterminado de $this->QueryTotal ), esto le permitirá simular la consulta función. No estoy seguro de si entendí correctamente la sintaxis de PHP, por lo que en caso de que no lo hiciera, lea esto como "Pseudo código":

public function validate($value, Constraint $constraint, $queryFunc=$this->QueryTotal)
{
    $totalCount =  $queryFunc($value);
    ShowMessageWhenTotalExceedsMaximum($totalCount,$constraint);
}
    
respondido por el Doc Brown 20.05.2016 - 09:45

Lea otras preguntas en las etiquetas