2012-05-28 117 views
41

我對測試世界相當陌生,我想確保自己走在正確的軌道上。如何使用PHPUnit在Symfony2中設置數據庫繁重的單元測試?

我想設立單元測試使用PHPUnit的一個Symfony2的項目。

PHPUnit正在工作,簡單的默認控制器測試工作正常。 (然而,這是不是功能測試,但單元測試我的應用程序)。

我的項目在很大程度上依賴於數據庫的交互,雖然,據我從phpunit's documentation明白,我應該成立專班根據\PHPUnit_Extensions_Database_TestCase,然後創建燈具爲我的分貝,並從那裏工作。

然而,symfony2只提供WebTestCase類,它只從\PHPUnit_Framework_TestCase開箱即用。

所以我是正確的假設,我要創造我自己的DataBaseTestCase其中大部分是副本WebTestCase,唯一的區別是,它從\PHPUnit_Extensions_Database_TestCase延伸,並實現其所有的抽象方法?

或者是否有關於數據庫中心測試的其他「內置」推薦工作流程symfony2

由於我想確保我的模型存儲和檢索正確的數據,我不想最終偶然測試準則的細節。

+0

我正在通過相同的問題。到目前爲止的運氣? –

+0

@JasonSwett沒有。由於缺乏令人滿意的答案,我剛剛開始賞金。 – k0pernikus

回答

2

TL;博士:

  • 當且僅當你想要去的全功能測試路線,那麼我建議仰視Sgoettschkes's answer
  • 如果你想單元測試你的應用程序並測試與數據庫交互的代碼,無論是閱讀還是直接跳轉到symfony2 docs


有在我原來的問題某些方面說清楚我缺乏對單元測試和功能測試之間差異的理解。 (正如我寫的,我想單元測試應用程序,但同時也在談論控制器測試;這些都是通過定義進行的功能測試)。

單元測試只適用於服務而不適用於存儲庫。這些服務可以使用實體管理器的模擬。

我的應用程序的實際使用情況實際上很好地反映在how to test code that interacts with the databse上的symfony2文檔中。他們的服務測試提供這個例子:

服務類:

use Doctrine\Common\Persistence\ObjectManager; 

class SalaryCalculator 
{ 
    private $entityManager; 

    public function __construct(ObjectManager $entityManager) 
    { 
     $this->entityManager = $entityManager; 
    } 

    public function calculateTotalSalary($id) 
    { 
     $employeeRepository = $this->entityManager 
      ->getRepository('AppBundle:Employee'); 
     $employee = $employeeRepository->find($id); 

     return $employee->getSalary() + $employee->getBonus(); 
    } 
} 

服務測試類:

namespace Tests\AppBundle\Salary; 

use AppBundle\Salary\SalaryCalculator; 
use AppBundle\Entity\Employee; 
use Doctrine\ORM\EntityRepository; 
use Doctrine\Common\Persistence\ObjectManager; 

class SalaryCalculatorTest extends \PHPUnit_Framework_TestCase 
{ 
    public function testCalculateTotalSalary() 
    { 
     // First, mock the object to be used in the test 
     $employee = $this->getMock(Employee::class); 
     $employee->expects($this->once()) 
      ->method('getSalary') 
      ->will($this->returnValue(1000)); 
     $employee->expects($this->once()) 
      ->method('getBonus') 
      ->will($this->returnValue(1100)); 

     // Now, mock the repository so it returns the mock of the employee 
     $employeeRepository = $this 
      ->getMockBuilder(EntityRepository::class) 
      ->disableOriginalConstructor() 
      ->getMock(); 
     $employeeRepository->expects($this->once()) 
      ->method('find') 
      ->will($this->returnValue($employee)); 

     // Last, mock the EntityManager to return the mock of the repository 
     $entityManager = $this 
      ->getMockBuilder(ObjectManager::class) 
      ->disableOriginalConstructor() 
      ->getMock(); 
     $entityManager->expects($this->once()) 
      ->method('getRepository') 
      ->will($this->returnValue($employeeRepository)); 

     $salaryCalculator = new SalaryCalculator($entityManager); 
     $this->assertEquals(2100, $salaryCalculator->calculateTotalSalary(1)); 
    } 
} 

對於那些類型的測試不需要測試數據庫中,只有(痛苦)的嘲諷。

由於重要的是測試業務邏輯,而不是持久層。

僅用於功能測試是有意義的有一個人應該建立和事後拆掉自己的測試數據庫,最大的問題應該是:

在做功能測試有意義嗎?

我以前認爲測試的所有東西是正確的答案;但在使用了很多舊版軟件後,本身幾乎沒有進行測試驅動開發,但我已經變得更加懶惰實用並且認爲某些功能正在運行,直到被錯誤證明爲止。

假設我有一個應用程序解析XML,創建一個對象,並將這些對象存儲到數據庫中。如果將對象存儲到數據庫的邏輯已知可以正常工作(如:公司需要數據並且至今尚未中斷),並且即使該邏輯是一大堆廢話,也沒有迫在眉睫需要測試。盡我所能確保我的XML解析器提取正確的數據。我可以從經驗中推斷出正確的數據將被存儲。

有些情況下,功能測試是非常重要的,即如果有人寫一個網上商店。在那裏,將購買物品存儲到數據庫中的業務至關重要,這裏使用整個測試數據庫進行功能測試是絕對有意義的。

0

你可以使用這個類:

<?php 

namespace Project\Bundle\Tests; 

require_once dirname(__DIR__).'/../../../app/AppKernel.php'; 

use Doctrine\ORM\Tools\SchemaTool; 

abstract class TestCase extends \PHPUnit_Framework_TestCase 
{ 
/** 
* @var Symfony\Component\HttpKernel\AppKernel 
*/ 
protected $kernel; 

/** 
* @var Doctrine\ORM\EntityManager 
*/ 
protected $entityManager; 

/** 
* @var Symfony\Component\DependencyInjection\Container 
*/ 
protected $container; 


public function setUp() 
{ 
    // Boot the AppKernel in the test environment and with the debug. 
    $this->kernel = new \AppKernel('test', true); 
    $this->kernel->boot(); 

    // Store the container and the entity manager in test case properties 
    $this->container = $this->kernel->getContainer(); 
    $this->entityManager = $this->container->get('doctrine')->getEntityManager(); 

    // Build the schema for sqlite 
    $this->generateSchema(); 


    parent::setUp(); 
} 

public function tearDown() 
{ 
    // Shutdown the kernel. 
    $this->kernel->shutdown(); 

    parent::tearDown(); 
} 

protected function generateSchema() 
{ 
    // Get the metadatas of the application to create the schema. 
    $metadatas = $this->getMetadatas(); 

    if (! empty($metadatas)) { 
     // Create SchemaTool 
     $tool = new SchemaTool($this->entityManager); 
     $tool->createSchema($metadatas); 
    } else { 
     throw new Doctrine\DBAL\Schema\SchemaException('No Metadata Classes to process.'); 
    } 
} 

/** 
* Overwrite this method to get specific metadatas. 
* 
* @return Array 
*/ 
protected function getMetadatas() 
{ 
    return $this->entityManager->getMetadataFactory()->getAllMetadata(); 
} 
} 

然後你就可以測試你的實體。這樣的事情(假設你有一個實體用戶)

//Entity Test 
class EntityTest extends TestCase { 

    protected $user; 

    public function setUp() 
    { 
     parent::setUp(); 
     $this->user = new User(); 
     $this->user->setUsername('username'); 
     $this->user->setPassword('p4ssw0rd'); 


     $this->entityManager->persist($this->user); 
     $this->entityManager->flush(); 

    } 

    public function testUser(){ 

     $this->assertEquals($this->user->getUserName(), "username"); 
     ... 

    } 

} 

希望得到這個幫助。

來源:theodo.fr/blog/2011/09/symfony2-unit-database-tests

+5

爲什麼你不給參考http://www.theodo.fr/blog/2011/09/symfony2-unit-database-tests/ –

+0

抱歉。 – Munir

34

我從來沒有使用過的PHPUnit_Extensions_Database_TestCase,因爲這兩個原因,主要是:

  • 一點也沒有」好規模。如果您爲每個測試設置並拆除數據庫,並且您有一個嚴重依賴數據庫的應用程序,則最終會一遍又一遍地創建和刪除相同的模式。
  • 我喜歡我的燈具不僅在我的測試中,而且在我的開發數據庫中,甚至還有一些燈具需要用於生產(初始管理用戶或產品類別或其他)。把它們放在一個只能用於phpunit的xml中對我來說並不合適。

我的理論方法...

我使用doctrine/doctrine-fixtures-bundle的燈具(不管是什麼目的),並建立整個數據庫的所有燈具。然後,我對這個數據庫執行所有測試,並確保在測試更改它時重新創建數據庫。

優點是我不需要再次設置數據庫如果一個測試只讀,但不改變任何東西。對於我必須做的更改,請將其刪除並重新創建,或確保恢復更改。

我使用sqlite進行測試,因爲我可以設置數據庫,然後複製sqlite文件並用一個乾淨的文件替換它以恢復原始數據庫。這樣我就不必刪除數據庫,創建它併爲一個乾淨的數據庫再次加載所有燈具。

......在代碼

我寫了一個article about how I do database tests with symfony2 and phpunit

儘管它使用sqlite我認爲可以很容易地做出更改使用MySQL或Postgres或其他。

思考進一步

下面是可能工作的一些其他的想法:

  • 我曾經讀過有關測試設置,您使用的數據庫,然後再開始交易(設置方法中)然後使用tearDown來回滾。這樣你就不需要再次設置數據庫,只需要初始化一次。
  • 上面描述的我的設置有一個缺點,即每次執行phpunit時都會設置數據庫,即使您只運行一些沒有數據庫交互的單元測試。我正在試驗一個設置,我使用一個全局變量來指示數據庫是否已經建立,然後在測試中調用一個方法來檢查這個變量,並在數據庫沒有發生的時候初始化它。這種方式只有在測試需要數據庫時纔會發生。
  • sqlite的一個問題是它在一些極少數情況下不能和MySQL一樣工作。我有一個問題曾經在MySQL和sqlite中表現不同的情況下導致測試在與MySQL一切正常工作時失敗。我不記得那是什麼。
+7

有一個包提供了PHPUnit和Doctrine的固定裝置之間的進一步集成,稱爲[Liip/FunctionalTestBundle](https://github.com/liip/LiipFunctionalTestBundle)。我們將它與在我們的config_test.yml配置中定義的sqlite數據庫結合使用,這有助於簡化很多事情!這個包的一個非常有用的特性是它可以緩存你的sqlite數據庫,這樣數據庫就不會在測試之間共享,並減少了爲每個測試創建和設置數據庫的開銷。 –

+0

關於SQLite和MySQL之間的區別,我認爲你遇到的第一件事是像NOW()這樣的日期和時間函數,它們在SQLite中不起作用。我在我的博客最近發佈了一篇關於使用SQLite進行單元測試的文章,其中描述了其中一些問題http://cvuorinen.net/2012/10/model-testing-using-sqlite-in-memory-database-with-zend-框架/ – Cvuorinen

+0

[Here's](http://php-and-symfony.matthiasnoback.nl/2011/10/symfony2-use-a-bootstrap-file-for-your-phpunit-tests-and-run-some-console -commands /)另一篇不錯的文章,展示如何使用Doctrine和Symfony進行測試 – naitsirch

相關問題