2016-02-27 124 views
-2

我正在使用Symfony 2.8(最新)的Web應用程序,其中可以單獨使用/重用的應用程序的每個部分都是自己的軟件包。例如,有一個NewsBundle,GalleryBundle,ContactBundle,AdminBundle(這是一個特例 - 它只是EasyAdminBundle收集特定包所提供特徵的包裝包),UserBundle(用於存儲用戶實體和模板的FOSUserBundle子包)PHPUnit測試分離

我的問題基本上是,單元測試最好的結構是什麼?

讓我再解釋一下:在我的UserBundle中,我想對我的FOSUserBundle實現進行測試。我有一個測試登錄頁面(通過HTTP狀態碼),登錄失敗(通過錯誤消息),登錄成功(通過特定的代碼元素),記住我(通過Cookie),註銷(通過頁面-content)

<?php 

namespace myNamespace\Admin\UserBundle\Tests; 

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; 

/** 
* Class FOSUserBundleIntegrationTest. 
*/ 
class FOSUserBundleIntegrationTest extends WebTestCase 
{ 
    /** 
    * Tests the login, login "remember-me" and logout-functionality. 
    */ 
    public function testLoginLogout() 
    { 
     // Get client && enable to follow redirects 
     $client = self::createClient(); 
     $client->followRedirects(); 

     // Request login-page 
     $crawler = $client->request('GET', '/admin/login'); 

     // Check http status-code, form && input-items 
     $this->assertTrue($client->getResponse()->isSuccessful()); 
     $this->assertEquals(1, $crawler->filter('form[action="/admin/login_check"]')->count()); 
     $this->assertEquals(1, $crawler->filter('input[name="_username"]')->count()); 
     $this->assertEquals(1, $crawler->filter('input[name="_password"]')->count()); 
     $this->assertEquals(1, $crawler->filter('input[type="submit"]')->count()); 

     // Clone client and crawler to have the old one as template 
     $clientLogin = clone $client; 
     $crawlerLogin = clone $crawler; 

     // Get form 
     $formLogin = $crawlerLogin->selectButton('_submit')->form(); 

     // Set wrong user-data 
     $formLogin['_username'] = 'test'; 
     $formLogin['_password'] = '123'; 

     // Submit form 
     $crawlerLoginFailure = $clientLogin->submit($formLogin); 

     // Check for error-div 
     $this->assertEquals(1, $crawlerLoginFailure->filter('div[class="alert alert-error"]')->count()); 

     // Set correct user-data 
     $formLogin['_username'] = 'mmustermann'; 
     $formLogin['_password'] = 'test'; 

     // Submit form 
     $crawlerLoginSuccess = $client->submit($formLogin); 

     // Check for specific 
     $this->assertTrue(strpos($crawlerLoginSuccess->filter('body')->attr('class'), 'easyadmin') !== false ? true : false); 
     $this->assertEquals(1, $crawlerLoginSuccess->filter('li[class="user user-menu"]:contains("Max Mustermann")')->count()); 
     $this->assertEquals(1, $crawlerLoginSuccess->filter('aside[class="main-sidebar"]')->count()); 
     $this->assertEquals(1, $crawlerLoginSuccess->filter('div[class="content-wrapper"]')->count()); 

     // Clone client from template 
     $clientRememberMe = clone $client; 
     $crawlerRememberMe = clone $crawler; 

     // Get form 
     $formRememberMe = $crawlerRememberMe->selectButton('_submit')->form(); 

     // Set wrong user-data 
     $formRememberMe['_username'] = 'mmustermann'; 
     $formRememberMe['_password'] = 'test'; 
     $formRememberMe['_remember_me'] = 'on'; 

     // Submit form 
     $crawlerRememberMe = $clientRememberMe->submit($formRememberMe); 

     // Check for cookie 
     $this->assertTrue($clientRememberMe->getCookieJar()->get('REMEMBERME') != null ? true : false); 

     // Loop all links on page 
     foreach ($crawlerRememberMe->filter('a')->links() as $link) { 
      // Check for logout in uri 
      if (strrpos($link->getUri(), 'logout') !== false) { 
       // Set logout-link 
       $logoutLink = $link; 

       // Leave loop 
       break; 
      } 
     } 

     // Reuse client to test logout-link 
     $logoutCrawler = $clientRememberMe->click($logoutLink); 

     // Get new client && crawl default-page 
     $defaultPageClient = self::createClient(); 
     $defaultPageCrawler = $defaultPageClient->request('GET', '/'); 

     // Check http status-code, compare body-content 
     $this->assertTrue($defaultPageClient->getResponse()->isSuccessful()); 
     $this->assertTrue($logoutCrawler->filter('body')->text() == $defaultPageCrawler->filter('body')->text()); 
    } 
} 

所有這些測試將在一個方法來完成,因爲如果我在不同的方法做,我將有一個高的量(5×4行= 20行復制重複的代碼粘貼&)。這是否遵循最佳實踐?分離單元測試的最佳做法是什麼? (或其他措辭:你會怎麼做?)

問題的第二部分:是否有可能爲測試類或類似的工作提供幫助函數?我的意思是提供登錄客戶端的方法。這將用於管理功能測試。

+0

爲什麼評價如此糟糕?我能做些什麼來改善我的問題? – SebTM

+0

因爲這個問題非常廣泛,主要是基於意見的,並且沒有明確的答案。你會想要一些更具體的問題,以及你嘗試過的一些代碼示例。 –

+0

我已經添加了當前的代碼並更新了文本。你說得對,這可能是基於意見的,但我沒有找到關於如何管理代碼的「最佳實踐」。這將有助於我從社區體驗中受益,因爲這裏有許多專業開發人員。 – SebTM

回答

1

現在你的問題更具體一些,我會提供一個答案和一些解釋。你爲第一次測試做的事情可能會起作用,但不是你應該測試的方式。這不是最好的實踐,因爲它是繞過單元測試的想法,檢查針對單個工作單元的假設。你的測試有幾個「單位」的工作正在測試,他們應該都在單獨的測試。

這裏是一個比較合適的測試來爲前兩種情況的濃縮例如:

public function testLoginForm() 
{ 
    $client  = self::createClient(); 
    $crawler = $client->request('GET', '/admin/login'); 

    $this->assertTrue($client->getResponse()->isSuccessful()); 
    $this->assertEquals(1, $crawler->filter('form[action="/admin/login_check"]')->count()); 
    $this->assertEquals(1, $crawler->filter('input[name="_username"]')->count()); 
    $this->assertEquals(1, $crawler->filter('input[name="_password"]')->count()); 
    $this->assertEquals(1, $crawler->filter('input[type="submit"]')->count()); 
} 

public function testLoginFailure() 
{ 
    $client  = self::createClient(); 
    $crawler = $client->request('GET', '/admin/login'); 
    $form  = $crawler->selectButton('_submit')->form(); 

    $form['_username'] = 'test'; 
    $form['_password'] = '123'; 

    $crawler = $client->submit($form); 

    $this->assertEquals(1, $crawler->filter('div[class="alert alert-error"]')->count()); 
} 

有幾件事情在這裏。

  1. 您擔心代碼重複和額外的代碼行,但我只創建了兩個單獨的測試,根本沒有增加行數。我能夠刪除followRedirects()調用,因爲它不適用於這些測試,並且我通過簡單地重新創建客戶端和爬蟲程序來消除了兩行克隆,這是不太令人困惑的。
  2. 使用您的代碼只有一個單元測試,但如果該測試失敗,則可能出於多種不同原因 - 登錄失敗,登錄成功等。因此,如果該測試失敗,則必須篩選錯誤消息並找出你的系統哪部分失敗。通過分離測試,當測試失敗時,您只需通過測試名稱即可知道出了什麼問題。
  3. 您可以通過分離測試來消除一些冗餘代碼註釋:// Set wrong user-data不再需要,因爲測試本身被稱爲testLoginFailure()

它不僅是單元測試的最佳實踐,但是還有另外一個需要注意的,當涉及到使用WebTestCase,在你想要所有的測試中分離的。我試着製作一個靜態的$client變量,整個類都可以使用,認爲如果我只實例化一個實例,會節省內存/時間,但是當您開始運行多個測試時,這會導致不可預知的行爲。你想讓你的測試獨立發生。

您也可以使用setUp() and tearDown()功能,並有$this->client$this->crawler每個請求之前實例化,如果你真的想消除冗餘代碼:

use Symfony\Bundle\FrameworkBundle\Client; 
use Symfony\Component\DomCrawler\Crawler; 

/* 
* @var Client 
*/ 
private $client; 

/* 
* @var Crawler 
*/ 
private $crawler; 

/* 
* {@inheritDoc} 
*/ 
protected function setUp() 
{ 
    $this->client = self::createClient(); 
    $this->crawler = $this->client->request('GET', '/admin/login'); 
} 

/* 
* {@inheritDoc} 
*/ 
protected function tearDown() 
{ 
    unset($this->client); 
    unset($this->crawler); 
} 

...但此時你創建類級聲明這些變量的代碼,實例化它們,並將它們撕下來。您最後還會加入添加大量額外的代碼,這是您首先要避免的。此外,您的整個測試課程現在都僵化而且不靈活,因爲您永遠不會請求登錄頁面以外的頁面。另外,PHPUnit自身規定:

測試用例對象的垃圾回收不可預測。

上述聲明是在考慮到,如果你不記得手動清理你的測試。因此,除了上述其他的原因之外,您可能會遇到意外的行爲。

至於你的第二個問題,當然,提供幫助函數或擴展現有的*TestCase類。 Symfony文檔甚至爲此提供了一個例子private function that logs in a user。你可以把它放在一個單獨的測試類中,就像他們的文檔一樣,或者你可以製作你自己的具有這個功能的類。

TL; DR不要嘗試聰明與你的測試/測試的情況下,分開你的測試,並建立輔助功能或基礎測試用例類從,如果你重複使用很多相同設置的延伸。

+0

感謝您的幫助!恐怕我可以改進我的問題,以獲得像這樣的專業和有用的答案:) – SebTM

+0

沒問題 - 當您給出您嘗試的代碼示例時,審閱者確切地知道您的意思並幫助你。 –