如何测试不可注入的代码?


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只是POPO。

我的问题是,我不知道怎样才能模拟出的SearcherFactory(使用静态方法),也不是SearchEntityToQueryAdapter,它需要从结果SearcherFactory::getSearchDirector Search实例。如何在方法中注入从结果中构建的内容?也许有一些我不知道的设计模式?

谢谢你的帮助!


@DocBrown,它在$this->context->addViolation调用内部,中被使用if
iLikeBreakfast

1
一定是瞎子,对不起。
布朗

那么所有::都是静态的吗?
伊万

是的,在PHP中::是静态方法。
安迪

@Ewan是的,::在类上调用静态方法。
iLikeBreakfast

Answers:


11

有一些可能性,如何static在PHP中模拟方法,我使用的最好的解决方案是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
我相信迈克尔·费瑟(Michael Feather)在他的书“有效地使用旧版代码”中将其称为“自动换行”技术。
RubberDuck

1
说实话,@ RubberDuck我不确定它是否被称为代理。只要记得使用它,我就一直这样称呼它,羽毛先生的名字可能更合适,尽管我还没有读过这本书。
安迪

1
该类本身当然是“代理人”。依赖项打破技术称为“自动包裹” IIRC。我强烈推荐这本书。就像您在此处提供的一样,这里充满了宝石。
RubberDuck

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

这将使你在一个情况下,你可以考虑让那些两个新的方法公开和单元测试QueryTotalShowMessageWhenTotalExceedsMaximum独立。实际上,这里可行的选择是根本进行单元测试QueryTotal,因为您实际上只能测试ElasticSearch。为其编写单元测试ShowMessageWhenTotalExceedsMaximum应该很容易并且更有意义,因为它实际上可以测试您的业务逻辑。

但是,如果您希望直接测试“验证”,则考虑将查询函数本身作为参数传递给“验证”(默认值为$this->QueryTotal),这将允许您模拟查询函数。我不确定我是否正确使用了PHP语法,因此如果没有,请以“伪代码”的形式阅读:

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

我喜欢这个主意,但是我想让代码保持更多面向对象的风格,而不是像这样传递方法。
iLikeBreakfast

@iLikeBreakfast实际上,无论其他任何方式,这种方法都是好的。一种方法应尽可能短,并且一件事和一件事要做好(鲍勃叔叔,清洁代码)。这使得它更易于阅读,更易于理解和测试。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.