2012-04-30 16 views
8

Sono in esecuzione per alcuni problemi di velocità reale con PHPUnit/DBUnit. Tutto ciò che si estende per PHPUnit_Extensions_Database_TestCase impiega per sempre. Con 189 test, la suite impiega circa 8-9 minuti. Speravo che ci sarebbero voluti 30 secondi al massimo ;-)Come posso velocizzare l'esecuzione della suite di test PHPUnit + DBUnit?

Sembra che il ripristino del database al suo stato iniziale sia il processo che richiede tempo, quindi abbiamo ridotto i nostri dataset il più possibile e limitato il numero di tabelle che richiediamo per ogni caso di test. Sto usando i dispositivi e condividendo il più possibile.

Ci sono impostazioni o modifiche che posso utilizzare per accelerare l'esecuzione? Guardando cosa sta facendo il server MySQL durante i test, sembra che stia accadendo un sacco di troncamenti/inserimenti, ma sicuramente sarebbe più veloce impacchettare i dataset di test in tabelle temporanee e poi selezionarli semplicemente per ogni test?

Il driver che sto utilizzando è PDO/MySQL con un dataset di test XML.

+0

È necessario specificare dove si trova il collo di bottiglia. Probabilmente accelererà i tuoi bisogni se riuscirai a prendere in giro l'intero database in modo da non dover eseguire affatto dbunit. Un test dovrebbe essere eseguito al di sotto di un decimo di secondo, il che è in realtà considerevole lento per un test. – hakre

+0

Dato che non hai menzionato, stai usando Fixtures e condividendo il più possibile? – Pradeep

+0

Sto utilizzando gli apparecchi e condividendo il più ragionevolmente possibile. C'è un modo per profilare il corridore di prova? –

risposta

18

Su Google, sono riuscito a ridurre il tempo necessario da 10 minuti a 1 minuto. Risulta che la modifica di alcune impostazioni di configurazione di InnoDB in my.ini/my.cnf aiuterà.

L'impostazione innodb_flush_log_at_trx_commit = 2 sembra fare il lavoro. Dopo averlo modificato, riavvia il tuo server MySQL.

Altro su dev.mysql.com: innodb_flush_log_at_trx_commit

I comandi di impostazione come ACIDO compliant il lavaggio dei tronchi è. Il valore predefinito è 1 che corrisponde a ACID completo che significa

il buffer di registro viene scritto nel file di registro ad ogni commit di transazione e l'operazione di svuotamento su disco viene eseguita sul file di registro.

Con un valore di 2, avviene quanto segue:

Il buffer di registro viene scritto al fascicolo ad ogni commit, ma il filo un'operazione di disco non viene eseguita su esso.

La differenza chiave qui è che poiché il registro non è scritto ad ogni commit, un crash del sistema operativo o un'interruzione dell'alimentazione possono eliminarlo. Per la produzione, attenersi a un valore di 1. Per lo sviluppo locale con un database di test, il valore di 2 deve essere sicuro.

Se si lavora con i dati che verranno trasferite alla banca dati dal vivo, vorrei suggerire attaccare con il valore 1.

+0

Mille grazie - questo ha ridotto anche il mio tempo del 90%! Strano però visto che i miei tavoli sono MyISAM ... Forse sono i tavoli interni? – ChrisA

+2

Sì, questo sembra fare miracoli ... assicurati di metterlo sotto la sezione [mysqld] (non altrove). – anroots

+0

Questo non aiuta molto da pgsql :-(Penso che dbunit sux, sia estremamente lento .. – inf3rno

1

La creazione appuntamento fisso nel DBUnit è estremamente lento. Ci vogliono 1,5 secondi ogni volta con Core2Duo E8400 4GB Kingston 1333. È possibile trovare il collo di bottiglia con xdebug e risolvere il problema (se è possibile), o si può fare in uno dei seguenti:

1.)

È può essere eseguito solo i file di test attualmente sviluppa con un xml bootstrap personalizzato:

<?xml version="1.0" encoding="UTF-8"?> 
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:noNamespaceSchemaLocation="http://phpunit.de/phpunit.xsd" 
     backupGlobals="false" 
     verbose="true" 
     bootstrap="test/bootstrap.php"> 
    <testsuites> 
     <testsuite> 
      <directory>test/integration</directory> 
      <exclude>test/integration/database/RoleDataTest.php</exclude> 
     </testsuite> 
    </testsuites> 
    <php> 
     <env name="APPLICATION_MODE" value="test"/> 
    </php> 
</phpunit> 

La escludere una parte è importante qui. Puoi anche usare i gruppi di test.

2.)

namespace test\integration; 


abstract class AbstractTestCase extends \PHPUnit_Extensions_Database_TestCase 
{ 
    static protected $pdo; 
    static protected $connection; 

    /** 
    * @return \PHPUnit_Extensions_Database_DB_IDatabaseConnection 
    */ 
    public function getConnection() 
    { 
     if (!isset(static::$pdo)) { 
      static::$pdo = new \PDO('pgsql:host=localhost;port=5432;dbname=dobra_test', 'postgres', 'inflames', array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION)); 
      static::$connection = $this->createDefaultDBConnection(static::$pdo); 
     } 
     return static::$connection; 
    } 

    /** 
    * @return \PHPUnit_Extensions_Database_Operation_DatabaseOperation 
    */ 

    static protected $fixtureSet = false; 

    protected function getSetUpOperation() 
    { 
     $c = get_class($this; 
     if (!$c::$fixtureSet) { 
      $c::$fixtureSet = true; 
      return \PHPUnit_Extensions_Database_Operation_Factory::CLEAN_INSERT(true); 
     } 
     return \PHPUnit_Extensions_Database_Operation_Factory::NONE(); 
    } 

    static protected $dataSet; 

    /** 
    * @return \PHPUnit_Extensions_Database_DataSet_IDataSet 
    */ 
    public function getDataSet() 
    { 
     $c = get_class($this; 
     if (!isset($c::$dataSet)) { 
      $c::$dataSet = $this->createDataSet(); 
     } 
     return $c::$dataSet; 
    } 

    /** 
    * @return \PHPUnit_Extensions_Database_DataSet_IDataSet 
    */ 
    abstract protected function createDataSet(); 

    protected function dataSetToRows($tableName, array $ids) 
    { 
     $transformer = new DataSetRowsTransformer($this->getDataSet()); 
     $transformer->findRowsByIds($tableName, $ids); 
     $transformer->cutColumnPrefix(); 
     return $transformer->getRows(); 
    } 

} 

È possibile eseguire l'override del TestCase. In questo esempio si utilizzerà una sola connessione pdo per ogni caso di test (è possibile iniettarlo nel proprio codice con l'iniezione delle dipendenze), ignorando l'operazione di configurazione è possibile impostare l'apparecchio solo una volta per testcase o una sola volta per ogni test (dipende da self:: o $cls = get_class($this); $cls::). (PHPUnit ha una cattiva progettazione, crea una nuova istanza ad ogni chiamata di prova, quindi devi modificare i nomi delle classi per memorizzare le variabili per istanza o per classe.) In questo scenario devi scrivere i test per dipendere reciprocamente con l'annotazione @depend . Ad esempio è possibile eliminare la stessa riga creata nel test precedente.

Con questo codice di prova 1.5 secs invece di 6 x 1.5 = 9 secs:

namespace test\integration\database; 

use Authorization\PermissionData; 
use test\integration\AbstractTestCase; 
use test\integration\ArrayDataSet; 

class PermissionDataTest extends AbstractTestCase 
{ 
    static protected $fixtureSet = false; 
    static protected $dataSet; 

    /** @var PermissionData */ 
    protected $permissionData; 

    /** 
    * @return \PHPUnit_Extensions_Database_DataSet_IDataSet 
    */ 
    public function createDataSet() 
    { 
     return new ArrayDataSet(array(
      'permission' => array(
       array('permission_id' => '1', 'permission_method' => 'GET', 'permission_resource' => '^/$'), 
       array('permission_id' => '2', 'permission_method' => 'POST', 'permission_resource' => '^/$'), 
       array('permission_id' => '3', 'permission_method' => 'DELETE', 'permission_resource' => '^/$') 
      ), 
      'user' => array(
       array('user_id' => '1', 'user_name' => 'Jánszky László', 'user_email' => '[email protected]', 'user_salt' => '12435') 
      ), 
      'user_permission' => array(
       array('user_permission_id' => '1', 'user_id' => '1', 'permission_id' => '1'), 
       array('user_permission_id' => '2', 'user_id' => '1', 'permission_id' => '2') 
      ), 
      'role' => array(
       array('role_id' => '1', 'role_name' => 'admin') 
      ), 
      'role_permission' => array(
       array('role_permission_id' => '1', 'role_id' => '1', 'permission_id' => '1') 
      ), 
      'permission_cache' => array(
       array('permission_cache_id' => '1', 'user_id' => '1', 'permission_id' => '1'), 
       array('permission_cache_id' => '2', 'user_id' => '1', 'permission_id' => '2'), 
      ) 
     )); 
    } 

    public function testReadAllShouldReturnEveryRow() 
    { 
     $this->assertEquals($this->permissionData->readAll(), $this->dataSetToRows('permission', array(3, 2, 1))); 
    } 

    /** @depends testReadAllShouldReturnEveryRow */ 

    public function testReadAllByRoleIdShouldReturnEveryRowRelatedToRoleId() 
    { 
     $this->assertEquals($this->permissionData->readAllByRoleId(1), $this->dataSetToRows('permission', array(1))); 
    } 

    /** @depends testReadAllByRoleIdShouldReturnEveryRowRelatedToRoleId */ 

    public function testReadAllByUserIdShouldReturnEveryRowRelatedToUserId() 
    { 
     $this->assertEquals($this->permissionData->readAllByUserId(1), $this->dataSetToRows('permission', array(2, 1))); 
    } 

    /** @depends testReadAllByUserIdShouldReturnEveryRowRelatedToUserId */ 

    public function testCreateShouldAddNewRow() 
    { 
     $method = 'PUT'; 
     $resource = '^/$'; 
     $createdRow = $this->permissionData->create($method, $resource); 
     $this->assertTrue($createdRow['id'] > 0); 
     $this->assertEquals($this->getDataSet()->getTable('permission')->getRowCount() + 1, $this->getConnection()->getRowCount('permission')); 
     return $createdRow; 
    } 

    /** @depends testCreateShouldAddNewRow */ 

    public function testDeleteShouldRemoveRow(array $createdRow) 
    { 
     $this->permissionData->delete($createdRow['id']); 
     $this->assertEquals($this->getDataSet()->getTable('permission')->getRowCount(), $this->getConnection()->getRowCount('permission')); 
    } 

    /** @depends testDeleteShouldRemoveRow */ 

    public function testDeleteShouldRemoveRowAndRelations() 
    { 
     $this->permissionData->delete(1); 
     $this->assertEquals($this->getDataSet()->getTable('permission')->getRowCount() - 1, $this->getConnection()->getRowCount('permission')); 
     $this->assertEquals($this->getDataSet()->getTable('user_permission')->getRowCount() - 1, $this->getConnection()->getRowCount('user_permission')); 
     $this->assertEquals($this->getDataSet()->getTable('role_permission')->getRowCount() - 1, $this->getConnection()->getRowCount('role_permission')); 
     $this->assertEquals($this->getDataSet()->getTable('permission_cache')->getRowCount() - 1, $this->getConnection()->getRowCount('permission_cache')); 
    } 

    public function setUp() 
    { 
     parent::setUp(); 
     $this->permissionData = new PermissionData($this->getConnection()->getConnection()); 
    } 
} 

3.)

Un'altra soluzione per creare l'apparecchio solo una volta per ogni progetto, e dopo che l'uso ogni prova nelle transazioni e rollback dopo ogni test. (Questo non funziona se si ha il codice differito pgsql che richiede il commit per verificare i vincoli.)