2008-08-13 9 views
41

¿Hay alguna manera de hacer que las pruebas dentro de un TestCase se ejecuten en un orden determinado? 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.Ejecute pruebas de PHPUnit en cierto orden

+1

Puede añadir @Depends como se describe en una respuesta más abajo, y el uso de la configuración() y desmontaje() es también una buena idea, pero las pruebas solo se ejecutan de arriba a abajo ... – Andrew

+0

Un caso de uso adicional que no parece haberse cubierto: Quizás todas las pruebas sean atómicas, pero algunas pruebas son LENTAS. Quiero que las pruebas rápidas se ejecuten lo antes posible para que puedan fallar rápido, y las pruebas lentas que se ejecuten muertas al final, después de que ya haya visto otros problemas y pueda acceder a ellos de inmediato. – Kzqai

Respuesta

44

Quizás haya un problema de diseño en sus 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 debe 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?

+33

Esto no me parece correcto. El objetivo de una prueba unitaria es probar una unidad completa. El objetivo de tener una unidad es agrupar cosas que dependen unas de otras. Los exámenes de escritura que evalúan métodos individuales sin el contexto de la clase son similares a abogar por la programación de procedimientos sobre oo porque usted está defendiendo que las funciones individuales no deben depender de los mismos datos. – doliver

+3

No estoy de acuerdo con su punto de vista. El resultado de una prueba de creación de instancias es un objeto válido que puede ser utilizado por otras pruebas en su conjunto de pruebas. No es necesario crear una instancia de un nuevo objeto para cada prueba, especialmente si el constructor es complicado. – pedromanoel

+6

Si el constructor es complicado, estás haciendo algo mal, probablemente tu clase está haciendo demasiado. Por favor, lea sobre "SOLIDO", más específico sobre el "Patrón de Responsabilidad Individual (SRP)", también debe "falsificar" las dependencias en sus pruebas usando simulaciones, por favor lea también "burlas, falsificaciones y talones". –

1

Realmente existe un problema con las pruebas si necesitan ejecutarse en un orden determinado. 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).

Consulte this site para obtener toda una serie de ideas/información sobre cómo factorizar sus pruebas de manera que pueda evitar este tipo de problemas.

+1

PHPUnit admite dependencias de prueba a través de @depends. – mjs

8

Si desea que sus pruebas compartan varios objetos y configuraciones de ayudantes, puede usar setUp(), tearDown() para agregar a la propiedad sharedFixture.

+0

¿Aún puede 'assertEquals()', etc. en 'setUp()'? ¿Esa es una mala práctica? – jchook

118

PHPUnit admite dependencias de prueba a través de la anotación @depends.

Aquí se muestra un ejemplo de la documentación donde se llevará a cabo pruebas en un orden que satisface las dependencias, con cada prueba depende de pasar un argumento a la 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 las dependencias no resueltas se ejecutarán no (deseable, ya que esto llama la atención rápidamente a la prueba que falla). Por lo tanto, es importante prestar mucha atención cuando se usan dependencias.

+10

Para PHPUnit, esto significa que la función de prueba se omitirá si la prueba anterior no se ejecutó. Eso no crea un orden de prueba. – Dereckson

+3

Solo para expandir en @Dereckson, la anotación '@ depends' hará que se omita una prueba si aún no se ha ejecutado la prueba que depende de _either_ o falló cuando se ejecutó. –

+1

No resuelve el problema con la orden de prueba –

7

PHPUnit permite el uso de la anotación '@depends' que especifica casos de prueba dependientes y permite pasar argumentos entre casos de prueba dependientes.

2

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

b. testDestroyResource

<?php 
$app->destroyResource('resource'); 
$this->assertFalse($app->hasResource('resource')); 
?> 

creo que esto es una mala idea, ya que depende de testDestroyResource testCreateResource. Y una mejor práctica sería hacer

a. testCreateResource

<?php 
$app->createResource('resource'); 
$this->assertTrue($app->hasResource('resource')); 
$app->deleteResource('resource'); 
?> 

b.testDestroyResource

<?php 
$app->createResource('resource'); 
$app->destroyResource('resource'); 
$this->assertFalse($app->hasResource('resource')); 
?> 
+3

-1 En su segundo enfoque, destroyResource también depende de createResource, pero no está establecido explícitamente como tal. Si createResource falla, UTesting Framework señalará erróneamente que destroyResource no funciona – Tivie

1

solución alternativa: (!) Uso estática funciones en sus pruebas para crear elementos reutilizables. Por ejemplo (utilizo IDE selenio para pruebas de discos 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 utilizar 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 manera, se puede construir la jerarquía de ustedes pruebas.
  • Puede conservar la propiedad 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, el camino del cambio de inicio de sesión en el futuro, es suficiente modificar la clase LoginTest, y no encentras necesitará parte de inicio de sesión correcto en otras pruebas (que debería funcionar después de la actualización LoginTest) :)

Cuando ejecuto la prueba, el script se limpia hasta el comienzo. Arriba uso mi clase SeleniumClearTestCase (hago una captura de pantalla() y otras funciones agradables allí) es la extensión de MigrationToSelenium2 (desde github, para portar pruebas grabadas en firefox usando seleniumIDE + complemento de ff "Selenium IDE: Formateadores PHP") 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 elocuent también tengo en la función SeleniumClearTestCase createApplication (que se llama en setUp, y tomo esta función de laral test/TestCase)

+0

Aquí hay más detalles para ejecutar la prueba registrada en Selenium IDE en Laravel 5.2 y phpUnit: http://stackoverflow.com/questions/33845828/set-up- tests-with-phpunit-and-selenium/37890854 # 37890854 –

2

La respuesta correcta para esto es un archivo de configuración adecuado para las pruebas. Yo tenía el mismo problema y lo arreglaron mediante la creación de banco de pruebas con los archivos de prueba necesarios ordenar:

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> 
+0

Creo que esta es la única solución confiable – emfi

Cuestiones relacionadas