2014-01-31 66 views
1

我學習了TDD,並且我已經開始使用xSpec工具(語言無所謂,但在我的情況下,它是phpspec2)。我寫我的第一個規範:如何使用xSpec工具正確測試存儲庫?

<?php 

namespace spec\Mo\SpeechBundle\Controller; 

use Doctrine\Common\Collections\Collection; 
use PhpSpec\ObjectBehavior; 
use Prophecy\Argument; 
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface; 
use Symfony\Component\HttpFoundation\Response; 
use Mo\SpeechBundle\Repository\IdeaRepository; 
use Mo\SpeechBundle\Repository\SpeechRepository; 
use Mo\SpeechBundle\Entity\Idea; 
use Mo\SpeechBundle\Entity\Speech; 

class SpeechControllerSpec extends ObjectBehavior 
{ 
    function let(SpeechRepository $speechRepository, IdeaRepository $ideaRepository, EngineInterface $templating) 
    { 
     $this->beConstructedWith($speechRepository, $ideaRepository, $templating); 
    } 

    function it_is_initializable() 
    { 
     $this->shouldHaveType('Mo\SpeechBundle\Controller\SpeechController'); 
    } 

    function it_responds_to_show_action(EngineInterface $templating, Speech $speech, Response $response) 
    { 
     $templating 
      ->renderResponse('MoSpeechBundle:Speech:show.html.twig', ['speech' => $speech]) 
      ->willReturn($response) 
     ; 

     $this->showAction($speech)->shouldBeAnInstanceOf('Symfony\Component\HttpFoundation\Response'); 
    } 

    function it_responds_to_list_action(
     SpeechRepository $speechRepository, 
     IdeaRepository $ideaRepository, 
     EngineInterface $templating, 
     Response $response 
    ) 
    { 
     $speeches = [new Speech()]; 
     $ideas = [new Idea()]; 

     $speechRepository->findAll()->willReturn($speeches); 
     $ideaRepository->findAll()->willReturn($ideas); 

     $templating 
      ->renderResponse('MoSpeechBundle:Speech:list.html.twig', compact('speeches', 'ideas')) 
      ->willReturn($response) 
     ; 

     $this->listAction()->shouldBeAnInstanceOf('Symfony\Component\HttpFoundation\Response'); 
    } 

    function it_responds_list_by_idea_action(
     Idea $idea, 
     SpeechRepository $speechRepository, 
     IdeaRepository $ideaRepository, 
     EngineInterface $templating, 
     Response $response 
    ) 
    { 
     $speeches = [new Speech()]; 
     $ideas = [new Idea()]; 

     $speechRepository->findByIdea($idea)->willReturn($speeches); 
     $ideaRepository->findAll()->willReturn($ideas); 

     $templating 
      ->renderResponse('MoSpeechBundle:Speech:list.html.twig', compact('speeches', 'idea', 'ideas')) 
      ->willReturn($response) 
     ; 

     $this->listByIdeaAction($idea)->shouldBeAnInstanceOf('Symfony\Component\HttpFoundation\Response'); 
    } 
} 

對於控制器:

<?php 

namespace Mo\SpeechBundle\Controller; 

use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface; 
use Mo\SpeechBundle\Repository\IdeaRepository; 
use Mo\SpeechBundle\Repository\SpeechRepository; 
use Mo\SpeechBundle\Entity\Idea; 
use Mo\SpeechBundle\Entity\Speech; 

/** 
* Manages speeches. 
*/ 
class SpeechController 
{ 
    /** 
    * @var \Mo\SpeechBundle\Repository\SpeechRepository 
    */ 
    private $speechRepository; 

    /** 
    * @var \Mo\SpeechBundle\Repository\IdeaRepository 
    */ 
    private $ideaRepository; 

    /** 
    * @var \Symfony\Bundle\FrameworkBundle\Templating\EngineInterface 
    */ 
    private $templating; 

    /** 
    * @param \Mo\SpeechBundle\Repository\SpeechRepository $speechRepository 
    * @param \Mo\SpeechBundle\Repository\IdeaRepository $ideaRepository 
    * @param \Symfony\Bundle\FrameworkBundle\Templating\EngineInterface $templating 
    */ 
    public function __construct(SpeechRepository $speechRepository, IdeaRepository $ideaRepository, EngineInterface $templating) 
    { 
     $this->speechRepository = $speechRepository; 
     $this->ideaRepository = $ideaRepository; 
     $this->templating = $templating; 
    } 

    /** 
    * Shows speech. 
    * 
    * @param \Mo\SpeechBundle\Entity\Speech $speech 
    * 
    * @return \Symfony\Component\HttpFoundation\Response 
    */ 
    public function showAction(Speech $speech) 
    {   
     return $this->templating->renderResponse('MoSpeechBundle:Speech:show.html.twig', compact('speech')); 
    } 

    /** 
    * Shows list of speeches filtered by idea. 
    * 
    * @param \Mo\SpeechBundle\Entity\Idea $idea 
    * 
    * @return \Symfony\Component\HttpFoundation\Response 
    */ 
    public function listByIdeaAction(Idea $idea) 
    { 
     $speeches = $this->speechRepository->findByIdea($idea); 
     $ideas = $this->ideaRepository->findAll(); 

     return $this->templating->renderResponse('MoSpeechBundle:Speech:list.html.twig', compact('speeches', 'idea', 'ideas')); 
    } 

    /** 
    * Shows list of all speeches. 
    * 
    * @return \Symfony\Component\HttpFoundation\Response 
    */ 
    public function listAction() 
    { 
     $speeches = $this->speechRepository->findAll(); 
     $ideas = $this->ideaRepository->findAll(); 

     return $this->templating->renderResponse('MoSpeechBundle:Speech:list.html.twig', compact('speeches', 'ideas')); 
    } 
} 

好了,現在我敢肯定,指定我的控制器的這種行爲,我可以繼續前進。但我有另一個問題。

我使用存儲庫控制器規格的模擬,現在我想要寫規範的庫本身:

<?php 

namespace Mo\SpeechBundle\Repository; 

use Doctrine\ORM\EntityRepository; 
use Mo\SpeechBundle\Entity\Idea; 

class SpeechRepository extends EntityRepository 
{ 
    /** 
    * Finds all speeches by specified idea. 
    * 
    * @param \Mo\SpeechBundle\Entity\Idea $idea 
    * 
    * @return array 
    */ 
    public function findByIdea(Idea $idea) 
    { 
     return $this 
      ->createQueryBuilder('s') 
      ->leftJoin('s.ideas', 'i') 
      ->where('i = :idea') 
      ->setParameters(compact('idea')) 
      ->getQuery() 
      ->getResult() 
     ; 
    } 
} 

但規格描述的行爲,我的理解。如何正確地測試存儲庫,以便真正地返回我需要的內容,在我的案例中由想法進行演講。

我應該考慮使用xUnit工具(PHPUnit在PHP世界)創建功能測試嗎?或者我寫 te spec描述了我的存儲庫正確地創建查詢?或者我可以只爲所有應用程序使用Behat,不要關注這個問題。

+0

爲什麼當您在構造函數中使用DI時,您將庫傳遞給Controllerspec中的方法?你不能只調用「$ this-> repository」嗎? – user3746259

回答

2

我花了一個星期的時間分析這個問題,並找到了滿意的答案。

phpspec只指定我們的對象的行爲。而已。我們不能用它們創建功能測試。

所以,我們有兩種方法來測試我們的功能:

  1. 使用PHPUnit來寫的模塊和系統本身的功能測試。
  2. 使用Behat來描述我們的應用程序的功能。

PHPUnit,其他類似的框架和Behat有它們的陷阱和強大的一面。

如何使用,只能決定開發者。

1

我完全理解你的困境,我過去完全一樣。我認爲你的問題的基本答案是你的業務邏輯應該與任何框架(基礎架構代碼)分開,所以你不應該測試'Doctrine \ ORM \ EntityRepository'類型的對象。

我認爲最好的方法是在您的應用程序中有另一個層來保存您的業務邏輯,而這又可以使用適配器來從「Doctrine \ ORM \ EntityRepository」類型對象來回傳遞消息。這樣,您就可以完全規定您的業務規則(包括任何適配器),而無需測試原則上不應該進行測試的原則類型對象,因爲這是第三方代碼。

這樣做也可以讓您在將來更輕鬆地更改業務規則。

+0

謝謝你回答我的問題! 是的,我知道我不應該測試Doctrine的EntityRepository,因爲它的測試和我的應用程序不是關於Doctrine =) 好吧,我可以創建另一個圖層, G。 SpeechService,但它仍然會使用一些API來檢索數據:DBAL,EntityRepository,Mongo,REST API或其他任何東西。 在我的情況下,我使用知識庫,無關緊要地擴展EntityRepository或發送真正的請求到另一個Web服務,我需要確保它正確地發送請求或執行SQL查詢。 –

+0

所以,我可以檢查findByIdea()創建我需要的SQL,但我不確定它是否正確執行。 也許,我的問題是:「我可以使用phpspec2進行功能測試嗎?」 –

+0

我認爲phpspec提供的主要功能是業務邏輯的行爲測試。這是針對特定領域的具體邏輯。正如你所說,我不認爲它是用來進行功能測試的。正如他們在PHPSpec文檔中所說:重點在於行爲和設計,而不是驗證和結構。可能沒有多大幫助:p –