Zend_Test_PHPUnit proporciona un TestCase para aplicaciones
MVC que contiene aserciones para probar una variedad de
responsabilidades. Probablemente la manera más fácil de entender lo que puede
hacer es ver un ejemplo.
Ejemplo 68.1. Ejemplo de TestCase de inicio de sesión de una aplicación
Lo siguiente es un caso de prueba simple para un
UserController para verificar varias cosas:
El formulario de inicio de sesión debe mostrarse a los usuarios no autenticados.
Cuando un usuario inicia sesión, debe ser redirigido a su página de perfil, y esa página de perfil debe mostrar información relevante.
Este ejemplo en particular asume varias cosas. Primero, estamos moviendo la mayor parte de nuestro bootstrapping a un plugin. Esto simplifica la configuración del caso de prueba ya que nos permite especificar nuestro entorno de forma sucinta, y también nos permite arrancar la aplicación en una sola línea. Además, nuestro ejemplo en particular asume que el autoloading está configurado, por lo que no necesitamos preocuparnos por requerir las clases apropiadas (como el controlador correcto, el plugin, etc).
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
public function setUp()
{
$this->bootstrap = array($this, 'appBootstrap');
parent::setUp();
}
public function appBootstrap()
{
$this->frontController
->registerPlugin(new Bugapp_Plugin_Initialize('development'));
}
public function testCallWithoutActionShouldPullFromIndexAction()
{
$this->dispatch('/user');
$this->assertController('user');
$this->assertAction('index');
}
public function testIndexActionShouldContainLoginForm()
{
$this->dispatch('/user');
$this->assertAction('index');
$this->assertQueryCount('form#loginForm', 1);
}
public function testValidLoginShouldGoToProfilePage()
{
$this->request->setMethod('POST')
->setPost(array(
'username' => 'foobar',
'password' => 'foobar'
));
$this->dispatch('/user/login');
$this->assertRedirectTo('/user/view');
$this->resetRequest()
->resetResponse();
$this->request->setMethod('GET')
->setPost(array());
$this->dispatch('/user/view');
$this->assertRoute('default');
$this->assertModule('default');
$this->assertController('user');
$this->assertAction('view');
$this->assertNotRedirect();
$this->assertQuery('dl');
$this->assertQueryContentContains('h2', 'User: foobar');
}
}
Este ejemplo podría escribirse de una manera algo más simple -- no todas las aserciones mostradas son necesarias, y se proporcionan solo con fines ilustrativos. Esperamos que muestre lo simple que puede ser probar sus aplicaciones.
Como se señaló en el ejemplo
de inicio de sesión, todos los casos de prueba MVC deben extender
Zend_Test_PHPUnit_ControllerTestCase. Esta clase a su vez
extiende PHPUnit_Framework_TestCase, y le proporciona toda
la estructura y las aserciones que esperaría de PHPUnit -- así como algo de
andamiaje (scaffolding) y aserciones específicas para la implementación
MVC de Zend Framework.
Para probar su aplicación MVC, necesitará arrancarla (bootstrap).
Hay varias maneras de hacer esto, todas las cuales dependen de la propiedad pública
$bootstrap.
Primero, y probablemente lo más directo, simplemente cree una
instancia de Zend_Application como lo haría en su
index.php, y asígnela a la propiedad $bootstrap.
Normalmente, hará esto en su método setUp(); necesitará
llamar a parent::setUp() cuando termine:
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
public function setUp()
{
// Assign and instantiate in one step:
$this->bootstrap = new Zend_Application(
'testing',
APPLICATION_PATH . '/configs/application.ini'
);
parent::setUp();
}
}
Segundo, puede establecer esta propiedad para que apunte a un archivo. Si hace esto, el archivo no debería despachar el front controller, sino simplemente configurar el front controller y cualquier necesidad específica de la aplicación.
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
public $bootstrap = '/path/to/bootstrap/file.php'
// ...
}
Tercero, puede proporcionar un callback de PHP para ejecutar con el fin de arrancar su aplicación. Este método se ve en el ejemplo de inicio de sesión. Si el callback es una función o un método estático, esto podría establecerse a nivel de clase:
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
public $bootstrap = array('App', 'bootstrap');
// ...
}
En los casos en que se necesite una instancia de objeto, recomendamos realizar
esto en su método setUp():
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
public function setUp()
{
// Use the 'start' method of a Bootstrap object instance:
$bootstrap = new Bootstrap('test');
$this->bootstrap = array($bootstrap, 'start');
parent::setUp();
}
}
Observe la llamada a parent::setUp(); esto es necesario, ya que
el método setUp() de
Zend_Test_PHPUnit_ControllerTestCase realizará
el resto del proceso de arranque (que incluye llamar al
callback).
Durante el funcionamiento normal, el método setUp() arrancará
la aplicación. Este proceso primero incluirá limpiar el
entorno a un estado de solicitud limpio, restableciendo cualquier plugin y
helper, restableciendo la instancia del front controller, y creando nuevos
objetos de solicitud y respuesta. Una vez hecho esto, entonces
hará include() del archivo especificado en $bootstrap, o
llamará al callback especificado.
El bootstrapping debe estar lo más cerca posible de cómo se arrancará la aplicación. Sin embargo, hay varias advertencias:
No proporcione implementaciones alternativas de los objetos Request y Response; no se usarán.
Zend_Test_PHPUnit_ControllerTestCaseutiliza objetos de solicitud y respuesta personalizados,Zend_Controller_Request_HttpTestCaseyZend_Controller_Response_HttpTestCase, respectivamente. Estos objetos proporcionan métodos para configurar el entorno de solicitud de manera dirigida, y extraer los artefactos de respuesta de manera específica.No espere probar detalles específicos del servidor. En otras palabras, las pruebas no son una garantía de que el código se ejecutará en una configuración específica del servidor, sino simplemente que la aplicación debería ejecutarse como se espera si el enrutador puede enrutar la solicitud dada. Para este fin, no establezca cabeceras específicas del servidor en el objeto de solicitud.
Una vez que la aplicación esté arrancada, puede comenzar a crear sus pruebas.
Una vez que tenga su bootstrap en su lugar, puede comenzar a probar. Probar es básicamente como esperaría en una suite de pruebas de PHPUnit, con algunas pequeñas diferencias.
Primero, necesitará despachar una URL para probar, utilizando el
método dispatch() del TestCase:
class IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
// ...
public function testHomePage()
{
$this->dispatch('/');
// ...
}
}
Habrá ocasiones, sin embargo, en que necesitará proporcionar información
adicional -- variables GET y POST, información de COOKIE, etc. Puede
rellenar la solicitud con esa información:
class FooControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
// ...
public function testBarActionShouldReceiveAllParameters()
{
// Set GET variables:
$this->request->setQuery(array(
'foo' => 'bar',
'bar' => 'baz',
));
// Set POST variables:
$this->request->setPost(array(
'baz' => 'bat',
'lame' => 'bogus',
));
// Set a cookie value:
$this->request->setCookie('user', 'matthew');
// or many:
$this->request->setCookies(array(
'timestamp' => time(),
'host' => 'foobar',
));
// Set headers, even:
$this->request->setHeader('X-Requested-With', 'XmlHttpRequest');
// Set the request method:
$this->request->setMethod('POST');
// Dispatch:
$this->dispatch('/foo/bar');
// ...
}
}
Ahora que la solicitud se ha realizado, es momento de comenzar a hacer aserciones sobre ella.
![]() |
Importante |
|---|---|
El action helper redirector emite una sentencia |
Debido a su naturaleza, el plugin action helper redirector emite una redirección
y luego termina la ejecución. Debido a que no puede probar partes de una aplicación
que emiten llamadas exit, Zend_Test_PHPUnit_ControllerTestCase
deshabilita automáticamente la parte exit del redirector, ya que puede provocar que el
comportamiento de la prueba difiera de la aplicación real. Para asegurarse de que sus controladores
puedan probarse correctamente, utilice el redirector cuando necesite redirigir
al usuario a una página diferente:
class MyController extends Zend_Controller_Action
{
public function indexAction()
{
if($someCondition == true) {
return $this->_redirect(...);
} else if($anotherCondition == true) {
$this->_redirector->gotoSimple("foo");
return;
}
// do some stuff here
}
}
![]() |
Importante |
|---|---|
Dependiendo de su aplicación, esto puede no ser suficiente, ya que se podría ejecutar
lógica adicional de la acción, |
Las aserciones son el corazón de las pruebas unitarias; se utilizan para verificar
que los resultados sean los que se espera. Para este fin,
Zend_Test_PHPUnit_ControllerTestCase proporciona una serie de
aserciones para simplificar la prueba de sus aplicaciones y controladores MVC.
Los selectores CSS son una manera fácil de verificar que ciertos artefactos estén presentes en el contenido de la respuesta. También hacen que sea trivial asegurarse de que los elementos necesarios para las UI de Javascript y/o la integración AJAX estén presentes; la mayoría de los toolkits de JS proporcionan algún mecanismo para extraer elementos DOM basados en selectores CSS, por lo que la sintaxis sería la misma.
Esta funcionalidad se proporciona a través de Zend_Dom_Query, e integrada en un conjunto de aserciones "Query". Cada una de estas aserciones toma como primer argumento un selector CSS, con argumentos adicionales opcionales y/o un mensaje de error, según el tipo de aserción. Puede encontrar las reglas para escribir los selectores CSS en el capítulo de teoría de funcionamiento de Zend_Dom_Query. Las aserciones Query incluyen:
assertQuery($path, $message): afirma que uno o más elementos DOM que coinciden con el selector CSS dado están presentes. Si hay un$messagepresente, se antepondrá a cualquier mensaje de aserción fallida.assertQueryContentContains($path, $match, $message): afirma que uno o más elementos DOM que coinciden con el selector CSS dado están presentes, y que al menos uno contiene el contenido proporcionado en$match. Si hay un$messagepresente, se antepondrá a cualquier mensaje de aserción fallida.assertQueryContentRegex($path, $pattern, $message): afirma que uno o más elementos DOM que coinciden con el selector CSS dado están presentes, y que al menos uno coincide con la expresión regular proporcionada en$pattern. Si hay un$messagepresente, se antepondrá a cualquier mensaje de aserción fallida.assertQueryCount($path, $count, $message): afirma que hay exactamente$countelementos DOM que coinciden con el selector CSS dado presentes. Si hay un$messagepresente, se antepondrá a cualquier mensaje de aserción fallida.assertQueryCountMin($path, $count, $message): afirma que hay al menos$countelementos DOM que coinciden con el selector CSS dado presentes. Si hay un$messagepresente, se antepondrá a cualquier mensaje de aserción fallida. Nota: especificar un valor de 1 para$countes lo mismo que simplemente usarassertQuery().assertQueryCountMax($path, $count, $message): afirma que no hay más de$countelementos DOM que coinciden con el selector CSS dado presentes. Si hay un$messagepresente, se antepondrá a cualquier mensaje de aserción fallida. Nota: especificar un valor de 1 para$countes lo mismo que simplemente usarassertQuery().
Además, cada una de las anteriores tiene una variante "Not" que proporciona una
aserción negativa: assertNotQuery(),
assertNotQueryContentContains(),
assertNotQueryContentRegex(), y
assertNotQueryCount(). (Tenga en cuenta que las variantes min y
max no tienen estas variantes, por razones que deberían ser
obvias.)
Algunos desarrolladores están más familiarizados con XPath que con los selectores CSS, y por lo tanto también se proporcionan variantes en XPath de todas las aserciones Query. Estas son:
assertXpath($path, $message = '')assertNotXpath($path, $message = '')assertXpathContentContains($path, $match, $message = '')assertNotXpathContentContains($path, $match, $message = '')assertXpathContentRegex($path, $pattern, $message = '')assertNotXpathContentRegex($path, $pattern, $message = '')assertXpathCount($path, $count, $message = '')assertNotXpathCount($path, $count, $message = '')assertXpathCountMin($path, $count, $message = '')assertNotXpathCountMax($path, $count, $message = '')
A menudo una acción redirige. En lugar de seguir la redirección,
Zend_Test_PHPUnit_ControllerTestCase le permite
probar las redirecciones con un puñado de aserciones.
assertRedirect($message = ''): afirma simplemente que ha ocurrido una redirección.assertNotRedirect($message = ''): afirma que no ha ocurrido ninguna redirección.assertRedirectTo($url, $message = ''): afirma que ha ocurrido una redirección, y que el valor de la cabecera Location es la$urlproporcionada.assertNotRedirectTo($url, $message = ''): afirma que NO ha ocurrido una redirección, o que el valor de la cabecera Location NO es la$urlproporcionada.assertRedirectRegex($pattern, $message = ''): afirma que ha ocurrido una redirección, y que el valor de la cabecera Location coincide con la expresión regular proporcionada por$pattern.assertNotRedirectRegex($pattern, $message = ''): afirma que NO ha ocurrido una redirección, o que el valor de la cabecera Location NO coincide con la expresión regular proporcionada por$pattern.
Además de verificar las cabeceras de redirección, a menudo necesitará verificar códigos y cabeceras de respuesta HTTP específicos -- por ejemplo, para determinar si una acción resulta en una respuesta 404 o 500, o para asegurarse de que las respuestas JSON contengan la cabecera Content-Type apropiada. Las siguientes aserciones están disponibles.
assertResponseCode($code, $message = ''): afirma que la respuesta resultó en el código de respuesta HTTP dado.assertHeader($header, $message = ''): afirma que la respuesta contiene la cabecera dada.assertHeaderContains($header, $match, $message): afirma que la respuesta contiene la cabecera dada y que su contenido contiene la cadena dada.assertHeaderRegex($header, $pattern, $message): afirma que la respuesta contiene la cabecera dada y que su contenido coincide con la expresión regular dada.
Además, cada una de las aserciones anteriores tiene una variante "Not" para aserciones negativas.
A menudo es útil afirmar sobre la última acción, controlador y módulo ejecutados; además, es posible que desee afirmar sobre la ruta que se emparejó. Las siguientes aserciones pueden ayudarle en este sentido:
assertModule($module, $message = ''): Afirma que el módulo dado se utilizó en la última acción despachada.assertController($controller, $message = ''): Afirma que el controlador dado se seleccionó en la última acción despachada.assertAction($action, $message = ''): Afirma que la acción dada fue la última despachada.assertRoute($route, $message = ''): Afirma que la ruta nombrada dada fue emparejada por el enrutador.
Cada una también tiene una variante "Not" para aserciones negativas.
Saber cómo configurar su infraestructura de pruebas y cómo hacer aserciones es solo la mitad de la batalla; ahora es momento de comenzar a examinar algunos escenarios de prueba reales para ver cómo puede aprovecharlos.
Ejemplo 68.2. Probando un UserController
Consideremos una tarea estándar para un sitio web: autenticar y registrar usuarios. En nuestro ejemplo, definiremos un UserController para manejar esto, y tendremos los siguientes requisitos:
Si un usuario no está autenticado, siempre será redirigido a la página de inicio de sesión del controlador, independientemente de la acción especificada.
La página del formulario de inicio de sesión mostrará tanto el formulario de inicio de sesión como el formulario de registro.
Proporcionar credenciales inválidas debería resultar en volver al formulario de inicio de sesión.
Las credenciales válidas deberían resultar en una redirección a la página de perfil del usuario.
La página de perfil debe personalizarse para contener el nombre de usuario del usuario.
Los usuarios autenticados que visiten la página de inicio de sesión deben ser redirigidos a su página de perfil.
Al cerrar sesión, un usuario debe ser redirigido a la página de inicio de sesión.
Con datos inválidos, el registro debería fallar.
Podríamos, y deberíamos, definir más pruebas, pero estas servirán por ahora.
Para nuestra aplicación, definiremos un plugin, "Initialize", que
se ejecuta en routeStartup(). Esto nos permite encapsular
nuestro bootstrap en una interfaz OOP, que también proporciona una manera fácil
de proporcionar un callback. Veamos primero los aspectos básicos de esta
clase:
class Bugapp_Plugin_Initialize extends Zend_Controller_Plugin_Abstract
{
/**
* @var Zend_Config
*/
protected static $_config;
/**
* @var string Current environment
*/
protected $_env;
/**
* @var Zend_Controller_Front
*/
protected $_front;
/**
* @var string Path to application root
*/
protected $_root;
/**
* Constructor
*
* Initialize environment, root path, and configuration.
*
* @param string $env
* @param string|null $root
* @return void
*/
public function __construct($env, $root = null)
{
$this->_setEnv($env);
if (null === $root) {
$root = realpath(dirname(__FILE__) . '/../../../');
}
$this->_root = $root;
$this->initPhpConfig();
$this->_front = Zend_Controller_Front::getInstance();
}
/**
* Route startup
*
* @return void
*/
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
$this->initDb();
$this->initHelpers();
$this->initView();
$this->initPlugins();
$this->initRoutes();
$this->initControllers();
}
// definition of methods would follow...
}
Esto nos permite crear un callback de bootstrap como el siguiente:
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
public function appBootstrap()
{
$controller = $this->getFrontController();
$controller->registerPlugin(
new Bugapp_Plugin_Initialize('development')
);
}
public function setUp()
{
$this->bootstrap = array($this, 'appBootstrap');
parent::setUp();
}
// ...
}
Una vez que tengamos eso en su lugar, podemos escribir nuestras pruebas. Sin embargo, ¿qué
pasa con esas pruebas que requieren que un usuario esté conectado? La solución
fácil es usar nuestra lógica de aplicación para hacerlo... y hacer un pequeño ajuste
usando los métodos resetRequest() y
resetResponse(), que nos permitirán
despachar otra solicitud.
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
// ...
public function loginUser($user, $password)
{
$this->request->setMethod('POST')
->setPost(array(
'username' => $user,
'password' => $password,
));
$this->dispatch('/user/login');
$this->assertRedirectTo('/user/view');
$this->resetRequest()
->resetResponse();
$this->request->setPost(array());
// ...
}
// ...
}
Ahora escribamos las pruebas:
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
// ...
public function testCallWithoutActionShouldPullFromIndexAction()
{
$this->dispatch('/user');
$this->assertController('user');
$this->assertAction('index');
}
public function testLoginFormShouldContainLoginAndRegistrationForms()
{
$this->dispatch('/user');
$this->assertQueryCount('form', 2);
}
public function testInvalidCredentialsShouldResultInRedisplayOfLoginForm()
{
$request = $this->getRequest();
$request->setMethod('POST')
->setPost(array(
'username' => 'bogus',
'password' => 'reallyReallyBogus',
));
$this->dispatch('/user/login');
$this->assertNotRedirect();
$this->assertQuery('form');
}
public function testValidLoginShouldRedirectToProfilePage()
{
$this->loginUser('foobar', 'foobar');
}
public function testAuthenticatedUserShouldHaveCustomizedProfilePage()
{
$this->loginUser('foobar', 'foobar');
$this->request->setMethod('GET');
$this->dispatch('/user/view');
$this->assertNotRedirect();
$this->assertQueryContentContains('h2', 'foobar');
}
public function
testAuthenticatedUsersShouldBeRedirectedToProfileWhenVisitingLogin()
{
$this->loginUser('foobar', 'foobar');
$this->request->setMethod('GET');
$this->dispatch('/user');
$this->assertRedirectTo('/user/view');
}
public function testUserShouldRedirectToLoginPageOnLogout()
{
$this->loginUser('foobar', 'foobar');
$this->request->setMethod('GET');
$this->dispatch('/user/logout');
$this->assertRedirectTo('/user');
}
public function testRegistrationShouldFailWithInvalidData()
{
$data = array(
'username' => 'This will not work',
'email' => 'this is an invalid email',
'password' => 'Th1s!s!nv@l1d',
'passwordVerification' => 'wrong!',
);
$request = $this->getRequest();
$request->setMethod('POST')
->setPost($data);
$this->dispatch('/user/register');
$this->assertNotRedirect();
$this->assertQuery('form .errors');
}
}
Observe que estas son concisas, y, en su mayor parte, no buscan contenido real. En cambio, buscan artefactos dentro de la respuesta -- códigos de respuesta y cabeceras, y nodos DOM. Esto le permite verificar que la estructura sea la esperada -- evitando que sus pruebas fallen cada vez que se agrega contenido nuevo al sitio.
Observe también que utilizamos la estructura del documento en nuestras pruebas. Por ejemplo, en la prueba final, buscamos un formulario que tenga un nodo con la clase "errors"; esto nos permite probar simplemente la presencia de errores de validación del formulario, sin preocuparnos por cuáles errores específicos podrían haberse producido.
Esta aplicación puede utilizar una base de datos. Si es así, probablemente necesitará algo de andamiaje para asegurarse de que la base de datos esté en una configuración prístina y comprobable al comienzo de cada prueba. PHPUnit ya proporciona funcionalidad para hacerlo; lea sobre ello en la documentación de PHPUnit. Recomendamos usar una base de datos separada para pruebas frente a producción, y en particular recomendamos usar un archivo SQLite o una base de datos en memoria, ya que ambas opciones funcionan muy bien, no requieren un servidor separado, y pueden utilizar la mayoría de la sintaxis SQL.
![[Important]](images/important.png)