2014-02-13 2 views
6

컨트롤러에서 체인 콜을 제대로 Eloquent 모델로 모의하려고합니다. 내 컨트롤러에서는 의존성 삽입을 사용하여 모델에 액세스하여 모의하기 쉽도록해야하지만 체인 된 호출을 테스트하고 올바르게 작동하는 방법을 모르겠습니다. 이것은 PHPUnit과 Mockery를 사용하는 Laravel 4.1의 모든 것입니다.모의 실험에서 체인 메소드 호출 테스트

컨트롤러 :

<?php 

class TextbooksController extends BaseController 
{ 
    protected $textbook; 

    public function __construct(Textbook $textbook) 
    { 
     $this->textbook = $textbook; 
    } 

    public function index() 
    { 
     $textbooks = $this->textbook->remember(5) 
      ->with('user') 
      ->notSold() 
      ->take(25) 
      ->orderBy('created_at', 'desc') 
      ->get(); 

     return View::make('textbooks.index', compact('textbooks')); 
    } 
} 

컨트롤러 검사 :

<?php 

class TextbooksControllerText extends TestCase 
{ 
    public function __construct() 
    { 
     $this->mock = Mockery::mock('Eloquent', 'Textbook'); 
    } 

    public function tearDown() 
    { 
     Mockery::close(); 
    } 

    public function testIndex() 
    { 
     // Here I want properly mock my chained call to the Textbook 
     // model. 

     $this->action('GET', '[email protected]'); 

     $this->assertResponseOk(); 
     $this->assertViewHas('textbooks'); 
    } 
} 

내가 시험에서 $this->action() 호출하기 전에이 코드를 배치하여이를 달성하기 위해 노력했습니다.

$this->mock->shouldReceive('remember')->with(5)->once(); 
$this->mock->shouldReceive('with')->with('user')->once(); 
$this->mock->shouldReceive('notSold')->once(); 
$this->app->instance('Textbook', $this->mock); 

그러나이 결과는 Fatal error: Call to a member function with() on a non-object in /app/controllers/TextbooksController.php on line 28입니다.

나는 또한 트릭을 할 수 있기를 바래 끈적 인 대안을 시도했다.

$this->mock->shouldReceive('remember')->with(5)->once() 
    ->shouldReceive('with')->with('user')->once() 
    ->shouldReceive('notSold')->once(); 
$this->app->instance('Textbook', $this->mock); 

이 연쇄 된 메서드 호출을 Mockery로 테스트 할 때 최선의 방법은 무엇입니까?

+0

설명서를 읽어 보시기 바랍니다 https://github.com/padraic/mockery#mocking-demeter-chains-and-fluent -interfaces – Shakil

답변

3

나 자신을 테스트하기에 아주 새로운 해요, 그리고,하지만 난 할 틀린 것을 시험하는 사람들의 보급을보십시오. 메소드가하는 모든 것을 정확히 테스트한다면 테스트는 아니고 메소드를 두 번 작성하는 것입니다.

코드를 블랙 박스로 생각해야합니다. 테스트를 작성할 때 내부에서 무슨 일이 벌어지고 있는지 알지 못합니다. 주어진 입력과 출력을 기대하는 메소드를 호출하십시오. 때로는 특정 다른 효과가 발생했는지 확인해야 할 때 shouldReceive가 들어올 때가 있습니다.하지만이 콜렉션 체인 테스트보다 더 높은 수준입니다.이 코드가 수행하는 코드가 수행되었는지 테스트해야합니다. 정확히 코드 자체가 발생합니다. 따라서 콜렉션 체인을 어떻게 든 다른 방법으로 추출해야하며 해당 메서드가 호출되었는지 단순히 테스트해야합니다.

코드의 목적보다는 실제 작성된 코드를 테스트할수록 더 많은 문제가 발생합니다. 예를 들어 동일한 작업을 다른 방식으로 수행하도록 코드를 업데이트해야하는 경우 (remember(6)이 아닌 remember(5)) 해당 코드를 remember(6)이 호출되도록 테스트해야합니다. 그걸 전혀 시험하지 마라.

이 조언은 체인화 된 메서드에만 해당되는 것은 아니며, 주어진 메서드를 테스트 할 때 다양한 개체에 다양한 메서드가 호출되도록하는 것이 좋습니다.

  • 적색/녹색 :

    내가 용어를 싫어하는만큼

    '빨강, 녹색, 리팩토링은'당신은 당신의 시험 방법은 실패한 두 지점이 있기 때문에 여기를 고려해야 처음을 쓸 때 테스트에 실패하면 코드에이 두 가지가 모두 포함되어서는 안됩니다 (위의 내용을 보면 1-2 가지가 될 수 있습니다). 그렇다면 테스트를 작성하지 않고 코드를 작성하는 중입니다. 그리고 실제로 코드를 작성한 후 테스트 첫 번째 TDD에 대한 코드에 맞게 테스트를 작성한 것입니다.

  • 리팩터 : 코드를 먼저 작성한 다음 코드에 맞게 테스트했는지 (또는 어쨌든 코드가 마술처럼 효과가 있었음을 테스트에 기록 할 shouldReceives를 정확히 추측 할 수 있음). 그것은 나쁘다. 그러나 그것이 세계의 끝이 아니기 때문에 당신이 그것을했다고 가정 해 봅시다. 이제 리팩토링을해야하지만 테스트를 변경하지 않고는 리팩토링 할 수 없습니다. 테스트는 코드와 너무 밀접하게 결합되어 있으므로 리팩토링으로 인해 테스트가 중단됩니다. 다시 TDD에 대한 생각입니다.

테스트 첫 번째 TDD를 따르지 않더라도 테스트를 위반하지 않고 리팩터 단계를 수행 할 수 있어야합니다.

어쨌든, 그건 내 tuppence.

+0

또한이 질문에 직접 대답하지 않는다는 것을 알고 있지만, 코드의 더 넓은 지점에 답변하고 커뮤니티 전체에 도움이되는 좋은 대답이라고 생각합니다. – alexrussell

+1

예, 좋은 답변입니다. 실제로 그것을 통해 독서는 나를 훨씬 더 명백한 것처럼 보이기 때문에 약간 어리 석다 고 느꼈다. 최종 결과가 예상대로 테스트하고 해당 프로세스에 중요한 코드의 상위 수준 만 테스트하십시오. 내가 처음에 찾던 결과를 기술적으로 달성하기 때문에 아래에 답을 남겨 두겠다.하지만 잘못된 접근 방법이다. – Dwight

+0

우리는 질문의 양면에 모두 대답합니다 :) – alexrussell

1

나는이 기술을 발견했지만 그것을 좋아하지 않습니다. 그것은 매우 장황합니다. 이를 성취하기 위해서는 좀 더 깔끔하고 간단한 방법이 있어야한다고 생각합니다. 테스트에서

$this->collection = Mockery::mock('Illuminate\Database\Eloquent\Collection')->shouldDeferMissing(); 

: 생성자에서

이 모든 대답은 대부분의 사람들의 눈에 잘못 될 수있다

$this->mock->shouldReceive('remember')->with(5)->andReturn($this->mock); 
$this->mock->shouldReceive('with')->with('user')->andReturn($this->mock); 
$this->mock->shouldReceive('notSold')->andReturn($this->mock); 
$this->mock->shouldReceive('take')->with(25)->andReturn($this->mock); 
$this->mock->shouldReceive('orderBy')->with('created_at', 'DESC')->andReturn($this->mock); 
$this->mock->shouldReceive('get')->andReturn($this->collection); 
15

원래는 댓글 이었지만 코드가 읽기 쉽도록 대답으로 이동했습니다!

내가 중용 될 것이지만, 너무 @alexrussell's answer쪽으로 기울어 :

$this->mock->shouldReceive('remember->with->notSold->take->orderBy->get') 
    ->andRe‌​turn($this->collection); 
+1

이 작동하지만 코드 커버리지가 실패한 것으로 나타났습니다 (사용중인 경우) – dwenaus

+0

나는 그것을 알지 못해서 그것을 지적 해 주셔서 감사합니다. 테스트되는 유닛의 내부에 빨려 들어 가지 않는 또 다른 이유는 다음과 같습니다. – petercoles

+0

@petercoles 3 년 후, 그러나 $ this-> mock을 사용할 수 없습니다. 인스턴스화하는 방법? – Mehrdad