Pruebas de integración y funcionales
Pruebe cómo funcionan realmente los componentes en conjunto
Pruebas de integración y funcionales es una lección gratuita de PHP Academy en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de PHP Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de PHP Academy incluye 4 lecciones en total.
Más allá de las pruebas unitarias
Las pruebas unitarias demuestran que una clase funciona de forma aislada, con todas sus dependencias simuladas. Pero los mocks pueden engañar: la base de datos real, el enrutador del framework o la capa HTTP se comportan de manera diferente. Las pruebas de integración verifican dos o más componentes reales conectados entre sí; las pruebas funcionales ejercitan la aplicación de extremo a extremo a través de su punto de entrada público (una solicitud HTTP o un comando de consola). En esta lección aprenderá a situar y escribir ambos tipos.
La pirámide de pruebas
Un conjunto de pruebas saludable tiene forma de pirámide:
- Muchas pruebas unitarias: rápidas, aisladas y ejecutadas constantemente.
- Menos pruebas de integración: utilizan una BD, una cola y una caché reales, por lo que son más lentas.
- Pocas pruebas funcionales/E2E: cubren toda la pila, son las más lentas y las más frágiles.
Invertir esta estructura (un «cono de helado» compuesto principalmente por pruebas E2E) produce un conjunto de pruebas lento e inestable. Lleve la cobertura a niveles inferiores siempre que sea posible.
Integración: repositorio contra una BD real
Una prueba de integración para un repositorio utiliza una conexión real a la base de datos, no un mock, para detectar errores de SQL, del esquema y del mapeo. Utilice una base de datos de pruebas dedicada (o una SQLite en memoria que coincida lo suficiente con su dialecto) y un PDO real.
<?php
use PHPUnit\Framework\TestCase;
final class UserRepositoryTest extends TestCase
{
private \PDO $pdo;
protected function setUp(): void
{
$this->pdo = new \PDO('sqlite::memory:');
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT)');
}
public function test_persists_and_finds_a_user(): void
{
$repo = new UserRepository($this->pdo);
$id = $repo->create('ada@example.com');
self::assertSame('ada@example.com', $repo->find($id)->email);
}
}
Aislamiento: reversión de transacciones
Las pruebas de integración no deben filtrar estado entre sí. El patrón más rápido consiste en envolver cada prueba en una transacción y revertirla en tearDown(): la BD vuelve a un estado limpio sin volver a cargar los datos iniciales. (El truncado es la alternativa cuando el código bajo prueba confirma transacciones o utiliza DDL).
<?php
protected function setUp(): void
{
$this->pdo = TestDb::connection();
$this->pdo->beginTransaction();
}
protected function tearDown(): void
{
$this->pdo->rollBack(); // undo everything this test did
}
Fixtures y factories
Las pruebas necesitan datos. Evite los enormes volcados de SQL, que son frágiles; prefiera pequeñas factories que creen únicamente las filas que necesita una prueba, con valores predeterminados razonables que pueda sobrescribir en cada caso. Así, la intención de cada prueba queda explícita y sigue siendo resistente al crecimiento del esquema.
<?php
final class UserFactory
{
public static function create(\PDO $pdo, array $overrides = []): int
{
$data = array_merge(['email' => 'user'.uniqid().'@test.dev'], $overrides);
$stmt = $pdo->prepare('INSERT INTO users (email) VALUES (:email)');
$stmt->execute($data);
return (int) $pdo->lastInsertId();
}
}
Funcional: utilizar la aplicación como un cliente
Una prueba funcional dirige el kernel/enrutador real de la aplicación con una solicitud simulada y comprueba la respuesta: código de estado, encabezados y cuerpo. WebTestCase de Symfony y los asistentes de pruebas HTTP de Laravel hacen exactamente esto sin iniciar un servidor web real. A continuación se muestra la estructura de Symfony.
<?php
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class HealthControllerTest extends WebTestCase
{
public function test_health_endpoint_returns_ok(): void
{
$client = static::createClient();
$client->request('GET', '/health');
self::assertResponseIsSuccessful(); // 2xx
self::assertJson($client->getResponse()->getContent());
}
}
Comprobación de la respuesta
Las pruebas funcionales comprueban el comportamiento observable en el límite: estado, estructura JSON y efectos secundarios en la BD. A continuación, un POST crea un recurso; comprobamos tanto la respuesta HTTP como que el registro haya llegado realmente al almacenamiento, lo que demuestra que toda la pila cooperó.
<?php
public function test_creating_a_user_persists_it(): void
{
$client = static::createClient();
$client->request('POST', '/users', server: [
'CONTENT_TYPE' => 'application/json',
], content: json_encode(['email' => 'ada@example.com']));
self::assertResponseStatusCodeSame(201);
$repo = static::getContainer()->get(UserRepository::class);
self::assertNotNull($repo->findByEmail('ada@example.com'));
}
Simulación selectiva de los extremos
Ni siquiera las pruebas funcionales deberían llamar a proveedores de pagos reales ni enviar correos reales. Sustituya únicamente los servicios externos más externos: cámbielos en el contenedor de pruebas por implementaciones simuladas o en memoria, manteniendo reales su propio código, el enrutamiento y la BD. Así conservará la fidelidad de extremo a extremo sin llamadas de red inestables.
<?php
public function test_checkout_charges_via_gateway(): void
{
$client = static::createClient();
// Replace the real gateway binding with an in-memory fake:
static::getContainer()->set(PaymentGateway::class, new FakePaymentGateway());
$client->request('POST', '/checkout', content: json_encode(['cart' => 1]));
self::assertResponseIsSuccessful();
}
Conjuntos separados, velocidades separadas
Divida su phpunit.xml en conjuntos de pruebas para que el conjunto unitario rápido se ejecute con cada guardado y los conjuntos de integración/funcionales, más lentos, se ejecuten bajo demanda o en CI. Agrúpelos por directorio y selecciónelos con --testsuite.
<phpunit>
<testsuites>
<testsuite name="unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="integration">
<directory>tests/Integration</directory>
</testsuite>
<testsuite name="functional">
<directory>tests/Functional</directory>
</testsuite>
</testsuites>
</phpunit>El determinismo supera a la cobertura
La forma más rápida de perder la confianza en un conjunto de pruebas es permitir la inestabilidad. Haga que las pruebas de integración/funcionales sean deterministas:
- Fije el tiempo (inyecte un reloj) en lugar de llamar a
time(). - Inicialice la aleatoriedad; evite aserciones dependientes del orden en consultas sin ordenar.
- Restablezca el estado de la BD en cada prueba (reversión de transacciones/truncado).
- No dependa nunca del orden de ejecución de las pruebas.
Haga coincidir la BD de pruebas con producción
SQLite en memoria es rápida, pero las diferencias de dialecto (distinción entre mayúsculas y minúsculas, funciones JSON, aplicación de claves foráneas y tipos) pueden ocultar errores que solo aparecen en su motor real. Para el código que utiliza SQL específico del motor, ejecute las pruebas de integración contra el mismo motor que en producción, normalmente en un contenedor Docker desechable en CI, en lugar de utilizar un sustituto conveniente.
<?php
// Read connection from environment so CI points at a real Postgres/MySQL container
$dsn = getenv('TEST_DATABASE_URL') ?: 'sqlite::memory:';
$pdo = new \PDO($dsn);
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
// In CI, TEST_DATABASE_URL targets the same engine as production.
Comprobación rápida
¿Cómo debe tratar una prueba funcional a un proveedor de pagos externo?
Resumen
Ha aprendido a probar componentes conjuntamente:
- Las pruebas de integración utilizan BD y servicios reales para detectar lo que los mocks ocultan; aíslelas revirtiendo las transacciones.
- Las pruebas funcionales dirigen el kernel real mediante solicitudes simuladas y comprueban las respuestas y los efectos secundarios.
- Las factories crean datos mínimos; simule únicamente los límites externos más externos.
- Divida los conjuntos según su velocidad y mantenga deterministas las pruebas más lentas.
A continuación: medir la calidad de las pruebas mediante pruebas de mutación.
Preguntas frecuentes
¿La lección «Pruebas de integración y funcionales» es gratis?
Sí — el texto completo de «Pruebas de integración y funcionales» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de PHP Academy, actualiza a CoddyKit PRO. El curso de PHP Academy incluye 4 lecciones en total.
¿Qué aprenderé en «Pruebas de integración y funcionales»?
Pruebe cómo funcionan realmente los componentes en conjunto Practicas PHP Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar PHP Academy?
No se requiere experiencia previa. PHP Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.
¿Cuánto tiempo toma la lección «Pruebas de integración y funcionales»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de PHP Academy?
Sí. Cada lección de PHP Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- El flujo de trabajo del desarrollo guiado por pruebas
- Mocks y stubs con Mockery
- Pruebas de integración y funcionales
- Pruebas de mutación con Infection