tutorial - pruebas unitarias php
Ejecute pruebas de PHPUnit en cierto orden (8)
Tal vez haya un problema de diseño en tus pruebas.
Por lo general, cada prueba no debe depender de ninguna otra prueba, por lo que pueden ejecutarse en cualquier orden.
Cada prueba necesita crear instancias y destruir todo lo que necesita para ejecutarse, ese sería el enfoque perfecto, nunca debería compartir objetos y estados entre pruebas.
¿Puede ser más específico acerca de por qué necesita el mismo objeto para las pruebas de N?
¿Hay alguna manera de hacer que las pruebas dentro de un TestCase
ejecuten en un cierto orden? Por ejemplo, quiero separar el ciclo de vida de un objeto de la creación para usarlo en la destrucción, pero necesito asegurarme de que el objeto esté configurado antes de ejecutar las otras pruebas.
Si desea que sus pruebas compartan varios objetos y configuraciones de ayuda, puede usar setUp()
, tearDown()
para agregar a la propiedad sharedFixture
.
PHPUnit permite el uso de la anotación ''@depends'' que especifica casos de prueba dependientes y permite pasar argumentos entre casos de prueba dependientes.
En mi opinión, tome el siguiente escenario donde necesito probar la creación y la destrucción de un recurso en particular.
Inicialmente tenía dos métodos, a. testCreateResource y b. testDestroyResource
a. testCreateResource
<?php
$app->createResource(''resource'');
$this->assertTrue($app->hasResource(''resource''));
?>
segundo. testDestroyResource
<?php
$app->destroyResource(''resource'');
$this->assertFalse($app->hasResource(''resource''));
?>
Creo que esta es una mala idea, ya que testDestroyResource depende de testCreateResource. Y una mejor práctica sería hacer
a. testCreateResource
<?php
$app->createResource(''resource'');
$this->assertTrue($app->hasResource(''resource''));
$app->deleteResource(''resource'');
?>
segundo. testDestroyResource
<?php
$app->createResource(''resource'');
$app->destroyResource(''resource'');
$this->assertFalse($app->hasResource(''resource''));
?>
Realmente hay un problema con sus pruebas si necesitan ejecutarse en cierto orden. Cada prueba debe ser totalmente independiente de las demás: le ayuda con la localización de defectos y le permite obtener resultados repetibles (y por lo tanto, depurables).
Visite este sitio para obtener una gran cantidad de ideas / información sobre cómo factorizar sus pruebas de manera que evite este tipo de problemas.
PHPUnit admite dependencias de prueba a través de la anotación @depends .
Aquí hay un ejemplo de la documentación donde las pruebas se ejecutarán en un orden que satisfaga las dependencias, y cada prueba dependiente pase un argumento al siguiente:
class StackTest extends PHPUnit_Framework_TestCase
{
public function testEmpty()
{
$stack = array();
$this->assertEmpty($stack);
return $stack;
}
/**
* @depends testEmpty
*/
public function testPush(array $stack)
{
array_push($stack, ''foo'');
$this->assertEquals(''foo'', $stack[count($stack)-1]);
$this->assertNotEmpty($stack);
return $stack;
}
/**
* @depends testPush
*/
public function testPop(array $stack)
{
$this->assertEquals(''foo'', array_pop($stack));
$this->assertEmpty($stack);
}
}
Sin embargo, es importante tener en cuenta que las pruebas con dependencias no resueltas no se ejecutarán (es deseable, ya que esto atrae la atención rápidamente a la prueba que falla). Por lo tanto, es importante prestar mucha atención cuando se usan dependencias.
Solución alternativa: use funciones estáticas (!) En sus pruebas para crear elementos reutilizables. Por ejemplo (yo uso selenio IDE para grabar pruebas y phpunit-selenio (github) para ejecutar la prueba dentro del navegador)
class LoginTest extends SeleniumClearTestCase
{
public function testAdminLogin()
{
self::adminLogin($this);
}
public function testLogout()
{
self::adminLogin($this);
self::logout($this);
}
public static function adminLogin($t)
{
self::login($t, ''[email protected]'', ''pAs$w0rd'');
$t->assertEquals(''John Smith'', $t->getText(''css=span.hidden-xs''));
}
// @source LoginTest.se
public static function login($t, $login, $pass)
{
$t->open(''/'');
$t->click("xpath=(//a[contains(text(),''Log In'')])[2]");
$t->waitForPageToLoad(''30000'');
$t->type(''name=email'', $login);
$t->type(''name=password'', $pass);
$t->click("//button[@type=''submit'']");
$t->waitForPageToLoad(''30000'');
}
// @source LogoutTest.se
public static function logout($t)
{
$t->click(''css=span.hidden-xs'');
$t->click(''link=Logout'');
$t->waitForPageToLoad(''30000'');
$t->assertEquals(''PANEL'', $t->getText("xpath=(//a[contains(text(),''Panel'')])[2]"));
}
}
Ok, y ahora, puedo usar estos elementos reutilizables en otra prueba :) Por ejemplo:
class ChangeBlogTitleTest extends SeleniumClearTestCase
{
public function testAddBlogTitle()
{
self::addBlogTitle($this,''I like my boobies'');
self::cleanAddBlogTitle();
}
public static function addBlogTitle($t,$title) {
LoginTest::adminLogin($t);
$t->click(''link=ChangeTitle'');
...
$t->type(''name=blog-title'', $title);
LoginTest::logout($t);
LoginTest::login($t, ''[email protected]'',''hilton'');
$t->screenshot(); // take some photos :)
$t->assertEquals($title, $t->getText(''...''));
}
public static function cleanAddBlogTitle() {
$lastTitle = BlogTitlesHistory::orderBy(''id'')->first();
$lastTitle->delete();
}
- De esta forma, puedes construir una jerarquía de tus pruebas.
- Puede mantener la propiedad de acero de que cada caso de prueba esté totalmente separado de otro (si limpia DB después de cada prueba).
- Y lo más importante, si, por ejemplo, la forma de inicio de sesión cambia en el futuro, solo se modifica la clase LoginTest y no se necesita una parte de inicio de sesión correcta en otras pruebas (deberían funcionar después de la actualización de LoginTest) :)
Cuando ejecuto la prueba, el script se limpia y comienza desde el principio. Arriba uso mi clase SeleniumClearTestCase
(hago una captura de pantalla () y otras funciones agradables allí) es la extensión de MigrationToSelenium2
(de github, para portar pruebas grabadas en firefox usando el complemento seleniumIDE + ff "Selenium IDE: PHP Formatters") que es extensión de mi clase LaravelTestCase (es una copia de Illuminate / Foundation / Testing / TestCase pero no extiende PHPUnit_Framework_TestCase) que tiene laravel de instalación para tener acceso elocuente cuando queremos limpiar DB al final de la prueba) que es la extensión de PHPUnit_Extensions_Selenium2TestCase. Para configurar laravel eloquent, también tengo la función SeleniumClearTestCase createApplication (que se llama en setUp
, y tomo esta función de laral test / TestCase)
La respuesta correcta para esto es un archivo de configuración adecuado para las pruebas. Tuve el mismo problema y lo solucioné al crear una suite de prueba con el orden de los archivos de prueba necesarios:
phpunit.xml:
<phpunit
colors="true"
bootstrap="./tests/bootstrap.php"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
strict="true"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false"
stopOnRisky="false"
>
<testsuites>
<testsuite name="Your tests">
<file>file1</file> //this will be run before file2
<file>file2</file> //this depends on file1
</testsuite>
</testsuites>
</phpunit>