2016-01-26 14 views
17

Recentemente ho riscontrato un problema su un'applicazione live. Mi sono reso conto che avevo sempre più eccezioni e blocchi di concorrenza con un database.Come test unitario di lettura/scrittura simultanea con PHPUnit?

Fondamentalmente avvio una transazione che richiede un SELECT e un INSERT sulla stessa tabella da impegnare.

Ma poiché il carico è veramente pesante, ogni transazione blocca la tabella, nella maggior parte dei casi è così veloce da non causare problemi, ma c'è un punto in cui i blocchi iniziano ad attendere sempre di più.

Sono stato in grado di risolvere questo problema in qualche modo modificando le query.

Anche se, ora, mi piacerebbe scrivere alcuni test con PHPUnit per convalidare la mia correzione ed evitare eventuali regressioni.

Non sono stato in grado di trovare alcun materiale su come farlo.

Poiché PHP non è multi-thread, non ho idea di come sia possibile eseguire query concorrenti in un singolo test per la convalida.

Fondamentalmente, mi piacerebbe essere in grado di eseguire più chiamate in un singolo test per garantire che tutto sia ok.

So che potrei provare a fare alcuni test di alto livello interrogando direttamente il server http e caricare l'intera applicazione, ma dal momento che il mio problema proviene da una libreria standalone, vorrei piuttosto testarlo contro se stesso.

Qualche idea?

+0

Avere la stessa domanda – XuDing

+0

Non è possibile testare simultaneamente la concorrenza. Le uniche cose che puoi fare sono a) stress test e b) far rispettare le condizioni alle quali pensi che l'anomalia dovrebbe accadere (se possibile). Fondamentalmente non dovresti testare affatto il database, dovresti assicurarti che i white paper affermino che i tuoi algoritmi sono sicuri e, se vuoi essere ultra-sicuro, aggiungi test di stress specifici al più alto livello di test (al di fuori dell'applicazione). Per rispondere alla domanda specifica, è sempre possibile utilizzare più processi per eseguire operazioni simultanee, pertanto è consigliabile utilizzare biforcazioni manuali o librerie come kirsswallsmith/spork. – Etki

risposta

5

La risposta breve è che non esiste un modo valido per testare letture/scritture simultanee su un database effettivo con PHPUnit. Semplicemente non è lo strumento giusto per quel lavoro.

Ma ci sono alcuni aspetti di una buona soluzione per testare questo. Prima il codice può essere scritto per gestire ogni possibile scenario. Un sistema di database come Postgres fallirà immediatamente su blocchi e problemi di transazione. Per gestire la cosa con eleganza io uso il codice che assomigli a questo (pseudo-codice, utilizzato anche per rispondere another question):

begin transaction 
while not successful and count < 5 
    try 
     execute sql 
     commit 
    except 
     if error code is '40P01' or '55P03' 
      # Deadlock or lock not available 
      sleep a random time (200 ms to 1 sec) * number of retries 
     else if error code is '40001' or '25P02' 
      # "In failed sql transaction" or serialized transaction failure 
      rollback 
      sleep a random time (200 ms to 1 sec) * number of retries 
      begin transaction 
     else if error message is 'There is no active transaction' 
      sleep a random time (200 ms to 1 sec) * number of retries 
      begin transaction 
    increment count 

Quindi creare due serie di test: un insieme dovrebbe confermare il codice sta gestendo la situazione in modo corretto (cioè test unitari). L'altra serie di test è per l'ambiente (cioè test di integrazione/funzionali).

unit test

trovo che questa sia una situazione difficile da riprodurre in un test PHPUnit che si connette a un database, e l'utilizzo di un vero e proprio database non è appropriato per un vero e proprio test di unità. Invece, crea stub PDO e unit test che generano ogni tipo di eccezione del database. Ciò conferma il codice funziona come previsto, ma non verifica la concorrenza su una vera e propria banca dati, ad es .:

$iterationCount = 0; 
$db->runInTransaction(function() use (&$iterationCount) { 
    $iterationCount++; 
    if ($iterationCount === 1) { 
     $exception = new PDOExceptionStub('Deadlock'); 
     $exception->setCode('40P01'); 
     throw $exception; 
    } 
}); 

// First time fails, second time succeeds 
$this->assertEquals(2, $iterationCount, 'Expected 2 iterations of runInTransaction'); 

Scrivi una suite completa di test che non si connettono al DB, ma confermano la logica.

Integration Test

Come hai trovato, PHPUnit semplicemente non è lo strumento giusto per eseguire un test di carico.Non è appropriato per qualcosa di più complesso dei test sequenziali di unità e integrazione. È possibile eseguire più istanze di PHPUnit contemporaneamente per caricare più carico sul database. Tuttavia, ritengo che ciò vada al di là di quanto previsto, inoltre non aiuta a monitorare il database per eventuali problemi. Quindi non vedo alcun modo per aggirare i test di livello superiore che stai cercando di evitare.

Ma la libreria può essere testata senza eseguire l'applicazione completa. Vorrei creare l'applicazione più semplice possibile solo per testarlo. Può avere uno o più script CLI che si connettono a un database. Questi script possono essere generati più volte per caricare il database. Se aiuta, questo ti consente anche di avere un'app con codice aggiuntivo per monitorare il database stesso.

+0

grazie che mi ha aiutato molto. –