unitarias test pruebas integración example ejemplos crear correr php concurrency phpunit database-deadlocks

php - test - pruebas unitarias y de integración



¿Cómo realizar la prueba unitaria de lectura/escritura concurrente con PHPUnit? (1)

Recientemente encontré un problema en una aplicación en vivo. Me di cuenta de que tenía más y más excepciones de concurrencia y bloqueos con una base de datos.

Básicamente, comienzo una transacción que requiere un SELECT e INSERT en la misma tabla para confirmar.

Pero como la carga es muy pesada, cada transacción bloquea la tabla, en la mayoría de los casos es tan rápida que no causa ningún problema, pero hay un punto en el que las cerraduras comienzan a esperar cada vez más.

Pude arreglar un poco este problema al ajustar las consultas.

Aunque, ahora, me gustaría escribir algunas pruebas con PHPUnit para validar mi corrección y evitar cualquier regresión.

No pude encontrar ningún material sobre cómo hacer esto.

Dado que PHP no es multiproceso, no tengo idea de cómo podría ejecutar consultas simultáneas en una sola prueba para validar.

Básicamente, me gustaría poder ejecutar varias llamadas en una sola prueba para asegurar que todo esté bien.

Sé que podría intentar realizar algunas pruebas de alto nivel consultando directamente el servidor http y cargar toda la aplicación, pero como mi problema proviene de una biblioteca independiente, me gustaría probarla contra sí misma.

¿Algunas ideas?


La respuesta corta es que no hay una buena manera de probar las lecturas / escrituras concurrentes en una base de datos real con PHPUnit. Simplemente no es la herramienta adecuada para ese trabajo.

Pero hay algunas facetas para una buena solución para probar esto. Primero, el código puede (y debe) escribirse para manejar todos los problemas posibles. Un sistema de base de datos como Postgres fallará inmediatamente en problemas de bloqueos y transacciones. Para manejarlo elegantemente, uso un código que se parece a esto (pseudo-código, también usado para responder otra pregunta ):

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

Luego cree dos conjuntos de pruebas: un conjunto debe confirmar que el código está manejando las situaciones correctamente (es decir, pruebas unitarias). El otro conjunto de pruebas es para el entorno (es decir, pruebas de integración / funcionales).

Pruebas unitarias

Me parece que esta es una situación difícil de reproducir en una prueba de PHPUnit que se conecta a una base de datos, y el uso de una base de datos real no es apropiado para una prueba de unidad verdadera. En su lugar, cree apéndices de PDO y pruebas de unidad que arrojan todo tipo de excepción de base de datos. Esto confirma que el código funciona como se esperaba, pero no prueba la concurrencia en ninguna base de datos real , por ejemplo:

$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'');

Escriba un conjunto completo de pruebas que no se conecten a la base de datos, pero confirmen la lógica.

Pruebas de integración

Como ha descubierto, PHPUnit simplemente no es la herramienta adecuada para realizar una prueba de carga. No es apropiado para nada más complejo que las pruebas secuenciales de unidad e integración. Puede ejecutar varias instancias de PHPUnit simultáneamente para poner más carga en la base de datos. Sin embargo, encuentro que esto va más allá de lo que estaba destinado, y además no le ayuda a monitorear la base de datos en busca de problemas. Por lo tanto, no veo ninguna manera de evitar las pruebas de alto nivel.

Pero su biblioteca puede ser probada sin ejecutar su aplicación completa. Yo crearía la aplicación más simple posible solo para probarlo. Puede tener uno o más scripts de CLI que se conectan a una base de datos. Esos scripts pueden generarse varias veces para cargar la base de datos. O cree una página web simple con la biblioteca y use cualquiera de las muchas aplicaciones de prueba de carga que existen para probarla.