주사 불가능한 코드를 테스트하는 방법은 무엇입니까?


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
눈이 멀었을 것입니다. 죄송합니다.
Doc Brown

그래서 모든 :: 정적입니다?
Ewan

예, PHP에서는 ::정적 메소드를위한 것입니다.
Andy

@Ewan 예, ::클래스에서 정적 메소드를 호출합니다.
iLikeBreakfast

답변:


11

staticPHP에서 메소드 를 조롱하는 방법, posbilites 가 있습니다. 내가 사용한 최고의 솔루션은 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 솔직히 말해서 프록시라고 확실하지 않습니다. 그것이 내가 그것을 사용하는 것을 기억할 수있는 한, 그것은 Mr. Feather의 이름이 더 적합 할 것입니다. 그러나 나는이 책을 읽지 않았습니다.
Andy

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 실제로이 접근법은 다른 것에 관계없이 좋습니다. 방법은 가능한 짧아야하며 한 가지 일만 잘 수행해야합니다 (Bob Uncle, Clean Code ). 따라서 읽기 쉽고 이해하기 쉽고 테스트하기가 더 쉽습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.