Pregunta

¿Hay alguna manera de obtener las pruebas dentro de un TestCase ejecutar en un orden determinado?Por ejemplo, quiero separar el ciclo de vida de un objeto desde su creación hasta su uso y destrucción, pero debo asegurarme de que el objeto esté configurado antes de ejecutar las otras pruebas.

¿Fue útil?

Solución

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 una instancia y destruir todo lo que necesita para ejecutarse, ese sería el enfoque perfecto, nunca debes compartir objetos y estados entre pruebas.

¿Puede ser más específico acerca de por qué necesita el mismo objeto para N pruebas?

Otros consejos

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

A continuación se muestra un ejemplo de la documentación donde las pruebas se ejecutarán en un orden que satisfaga las dependencias, y cada prueba dependiente 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 dependencias no resueltas no ejecutarse (deseable, ya que esto llama la atención rápidamente sobre la prueba fallida).Por eso, es importante prestar mucha atención al utilizar dependencias.

La respuesta correcta para esto es un archivo de configuración adecuado para las pruebas.Tuve el mismo problema y lo solucioné creando un conjunto de pruebas 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>

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

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, tomemos el siguiente escenario en el que necesito probar la creación y destrucción de un recurso en particular.

Inicialmente tenía dos métodos, a.testCreateResource y b.pruebaDestroyResource

a.pruebaCrearRecurso

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

b.pruebaDestroyResource

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

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

a.pruebaCrearRecurso

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

b.pruebaDestroyResource

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

Solución alternativa:Utilice funciones estáticas (!) en sus pruebas para crear elementos reutilizables.Por ejemplo (uso selenium IDE para registrar pruebas y phpunit-selenium (github) para ejecutar pruebas 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, 'john.smith@gmail.com', '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]"));
    }
}

Bien, ahora puedo usar estos elementos reutilizables en otras pruebas :) 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, 'paris@gmail.com','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, puede construir una jerarquía de sus pruebas.
  • Puede mantener la propiedad de que cada caso de prueba está totalmente separado de los demás (si limpia la base de datos después de cada prueba).
  • Y lo más importante, si, por ejemplo, la forma de iniciar sesión cambia en el futuro, solo modifica la clase LoginTest y no necesita la parte de inicio de sesión correcta en otras pruebas (deberían funcionar después de actualizar LoginTest) :)

Cuando ejecuto la prueba, mi script limpia la base de datos desde el principio.Arriba uso mi SeleniumClearTestCase clase (hago captura de pantalla() y otras funciones interesantes allí) es una extensión de MigrationToSelenium2 (desde github, para transferir pruebas grabadas en Firefox usando el complemento seleniumIDE + ff "Selenium IDE:PHP Formatters") que es una extensión de mi clase LaravelTestCase (es una copia de Illuminate\Foundation esting estCase pero no extiende PHPUnit_Framework_TestCase) que configura laravel para tener acceso a eloquent cuando queremos limpiar la base de datos al final de la prueba), que es extensión de PHPUnit_Extensions_Selenium2TestCase.Para configurar laravel eloquent también tengo en SeleniumClearTestCase la función createApplication (que se llama en setUp, y tomo esta función de laral test/TestCase)

Realmente hay un problema con las pruebas si es necesario ejecutarlas 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).

Verificar este sitio para obtener una gran cantidad de ideas/información sobre cómo factorizar sus pruebas de una manera que evite este tipo de problemas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top