PHPUnit en Drupal 8 | Prueba del navegador

Texto

En este tutorial abarcaremos los conceptos básicos de las pruebas del navegador PHPUnit en Drupal 9. ¡Al final, deberías poder escribir tu primera prueba del navegador!

     Para este ejemplo, usaremos el módulo Rules, ya que podemos incluir algunas pruebas funcionales del navegador. Luego, el tutorial explicará cómo probar la interfaz de usuario de Rules, para asegurarse de que funcione correctamente.

     Prerrequisitos:

     Una instalación de Drupal 8 funcionando:

     - Cómo instalar cualquier versión Drupal 8 usando Composer
 

     - Cómo Instalar LAMP usando WSL2 en Windows 10 (1era Parte)

     Composer instalado:

     - Cómo instalar Composer 2 en Ubuntu 20.04

      Módulos instalados:

       RulesAdmin ToolbarPathauto

    Configuración para el tutorial

        Primero, necesitaremos asegurarnos de que el módulo  Rules esté instalado (descárguelo y agréguelo a su carpeta de módulos). PHPUnit es parte de las dependencias de Composer del núcleo de Drupal, así que asegúrese de haber ejecutado la instalación de composer en tu Drupal 8. Familiarícese con la ejecución de pruebas PHPUnit y asegúrese de habilitar BROWSERTEST_OUTPUT_DIRECTORY e printerClass en su archivo phpunit.xml como se explica en esa página.

   ¿Qué debería probar con BrowserTestBase?

        BrowserTestBase le ofrece una forma de probar comportamientos e interacciones basados en la web. Por ejemplo, si desea verificar que se requiere un determinado permiso antes de que un usuario pueda visitar una determinada página, debe crear un usuario sin ese permiso e intentar visitar la página, y luego comparar el resultado con otro intento con ese permiso. 

        BrowserTestBase satisface la necesidad de realizar pruebas de alto nivel basadas en solicitudes de integraciones entre varios subsistemas en Drupal. Por lo general, las pruebas de BrowserTestBase no deberían requerir temas o configuración específicos del sitio, aunque es posible escribir pruebas con esos requisitos. Otras herramientas como Behat podrían usarse para pruebas de comportamiento específicas del sitio.

        Finalmente, BrowserTestBase reemplaza el WebTestBase heredado basado en Simpletest. Si está migrando desde Simpletest de Drupal, le resultará relativamente fácil convertir las pruebas de WebTestBase a BrowserTestBase.

   Cómo funcionan las pruebas del navegador de Drupal

      La mayor parte de Drupal es una funcionalidad orientada a la web, por lo que es importante tener una forma de ejercitar estas funciones. Las pruebas del navegador crean una instalación completa de Drupal y un navegador web virtual y luego usa el navegador web virtual para guiar la instalación de Drupal a través de una serie de pruebas, tal como lo haría si lo estuviera haciendo a mano.

   Acerca del escenario de prueba del módulo Rules

        El módulo Reglas proporciona una interfaz de usuario para los administradores donde pueden crear reglas. Consiste en múltiples páginas y formularios que queremos probar, los cuales están protegidos con permisos de usuario para que solo los administradores puedan acceder a ellos. El escenario de prueba más simple está en rules/tests/src/Functional/UiPageTest.php.

    Descubriendo lo que necesitamos probar

        Si instala el módulo Reglas, puede seguir manualmente los pasos y ver lo que cree que debe probarse. Si todavia no haz trabajado con Rules, te enseño todo lo que necesitas asaber en este artículo Cómo crear nuestra primera regla con Rules

        Visite Configuración> Flujo de trabajo> Reglas donde debería ver una página que dice "Aún no hay una regla de reacción". en la tabla de reglas. Puede agregar reglas y configurarlas, pero como primer paso, queremos probar que esta página de descripción general del administrador realmente existe y dice que aún no hay reglas.

   Escribir un archivo de prueba

       Ahora es el momento de crear nuestras pruebas, que haremos en la carpeta rules / tests / src / Functional. PHPUnit encontrará automáticamente los archivos de prueba del navegador en la carpeta tests / src / Functional de un módulo.

     Hay cuatro pasos básicos involucrados en la creación de una prueba:

  • Crear la estructura (simplemente crear una clase que herede de \ Drupal \ Tests \ BrowserTestBase o una clase de prueba de navegador similar)
  • Inicializar el caso de prueba con cualquier creación o configuración de usuario que deba realizarse
  • Crear pruebas reales dentro del caso de prueba
  • Y, por supuesto, intentar desesperadamente averiguar por qué nuestra prueba no funciona de la manera que esperamos y depurar la prueba (y quizás el módulo)

     Para empezar con la creación de nuestro archivo de pruebas, necesitaremos extender la clase BrowserTestBase.

namespace Drupal\Tests\rules\Functional;

use Drupal\Tests\BrowserTestBase;

/**
 * Tests that the Rules UI pages are reachable.
 *
 * @group rules_ui
 */
class UiPageTest extends BrowserTestBase {

     Como siguiente paso, debemos especificar la lista de módulos que deben habilitarse para la ejecución de prueba. En nuestro caso, esto es, por supuesto, las Rules.

  /**
   * Modules to enable.
   *
   * @var array
   */
  protected static $modules = ['node', 'rules'];

   Luego viene el setUp() opcional . Aquí es donde debemos hacer todo lo que sea necesario para que esta instancia de Drupal funcione como queremos. Tenemos que pensar: "¿Qué tuve que hacer para pasar de una instalación de Drupal de stock a donde puedo ejecutar esta prueba?". No todos los casos de prueba necesitarán esto, pero aquí hay un ejemplo en el que preparamos un tipo de contenido (tomado de las reglas ConfigureAndExecuteTest):

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    // Create an article content type that we will use for testing.
    $type = $this->container->get('entity_type.manager')->getStorage('node_type')
      ->create([
        'type' => 'article',
        'name' => 'Article',
      ]);
    $type->save();
    $this->container->get('router.builder')->rebuild();
  }

   Tenga en cuenta que si implementa el método setUp(), empiece por ejecutar el método parent::setUp() como en el ejemplo.

   Crear prueba específica: la página de reglas de reacción 

     Ahora necesitamos crear pruebas específicas para ejercitar el módulo. Simplemente creamos métodos de nuestra clase de prueba, cada uno de los cuales realiza una prueba en particular. Todos los métodos deben comenzar con 'prueba' en minúsculas. Cualquier método con visibilidad pública que comience de esta manera será reconocido automáticamente por PHPUnit y se ejecutará cuando se solicite.

     Nuestra primera prueba comprobará la página en admin/config/workflow/rules:

  /**
   * Tests that the reaction rule listing page works.
   */
  public function testReactionRulePage() {
    $account = $this->drupalCreateUser(['administer rules']);
    $this->drupalLogin($account);

    $this->drupalGet('admin/config/workflow/rules');
    $this->assertSession()->statusCodeEquals(200);

    // Test that there is an empty reaction rule listing.
    $this->assertSession()->pageTextContains('There is no Reaction Rule yet.');
  }

   setUp() y pruebas individuales

     Cada función de prueba tendrá una instancia de Drupal completamente nueva para ejecutar pruebas. Esto significa que lo que haya creado en una función de prueba anterior ya no estará disponible en la siguiente.

     Considere este ejemplo, tomado de Rules UIPageTest.php -extraction:

namespace Drupal\Tests\rules\Functional;

class UiPageTest extends RulesBrowserTestBase {

 /**
   * Modules to enable.
   *
   * @var array
   */
  protected static $modules = ['rules'];

  /**
   * {@inheritdoc}
   */
  protected function setUp() {
    parent::setUp();
    // ....
  }
  public function testReactionRulePage() {
    $account = $this->drupalCreateUser(['administer rules']);
    $this->drupalLogin($account);
    // .....
  }

  public function testCreateReactionRule() {
    $account = $this->drupalCreateUser(['administer rules']);
    $this->drupalLogin($account);
    // .....
  }

  public function testCancelExpressionInRule() {
    // Setup a rule with one condition.
    $this->testCreateReactionRule();
    // .....
  }

}

     Aqui ambos testReactionRulePage() y testCreateReactionRule deben crear su propia $account, porque ambas pruebas se ejecutan en sus propias instancias de Drupal y el Drupal de la última función no tiene cuentas a menos que se cree.

     Sin embargo, otras pruebas (posteriores) pueden depender del contenido creado en otras funciones de prueba si ejecutan esas funciones por separado, como lo hace testCancelExpressionInRule ().

     Por otro lado, la función setUp() se ejecuta antes de cada función de prueba, por lo que también puede usarlo para preparar el entorno de prueba.

   drupalGet() y Assertions

       El código anterior hizo una solicitud GET muy simple en la página admin / config / workflow / rules. Carga la página, luego verifica el código de respuesta y afirma que encontramos el texto apropiado en la página.

       La mayoría de las pruebas seguirán este patrón:

  1. Haga un $ this-> drupalGet ('some / path') para ir a una página
  2. Use $ this-> clickLink (..) para navegar por enlaces en la página
  3. Use $ this-> getSession () -> getPage () -> fillField (...); para completar los campos del formulario
  4. Envíe formularios con $ this-> getSession () -> getPage () -> pressButton (...); o use $ this-> submitForm (...); (o use el método obsoleto drupalPostForm ())
  5. Haga una o más afirmaciones para comprobar que lo que vemos en la página es lo que deberíamos ver.

   Rellenar formularios

    $page = $this->getSession()->getPage();

    $page->fillField('form_element', 'value');
    $page->selectFieldOption('form_element', 'value')

   Assertions (Afirmaciones)

       Hay decenas de posibles assertions. A continuación se muestran algunos ejemplos. Cuando vaya más allá de este tutorial, querrá mirar la clase \ Behat \ Mink \ WebAssert y su hijo de Drupal \ Drupal \ Tests \ WebAssert para leer más sobre ellos.

    $this->assertOptionSelected('form_element', 'value');
    $this->assertFieldByName('form_element');

    $web_assert = $this->assertSession();

    $web_assert->addressEquals('node/1');
    $web_assert->pageTextContains("Expected text");
    $web_assert->pageTextNotContains("Unexpected text");

   Ejecutando la prueba

     Usaremos la línea de comando para ejecutar la prueba. Esto se documenta con más detalle en la ejecutando la página de pruebas PHPUnit.

     Ejecutemos solo el método testReactionRulePage () de UiPageTest:

cd core
../vendor/bin/phpunit --filter testReactionRulePage ../modules/rules/tests/src/Functional/UiPageTest.php
PHPUnit 4.8.11 by Sebastian Bergmann and contributors.

.

Time: 25.14 seconds, Memory: 6.00Mb

OK (1 test, 5 assertions)

HTML output was generated
http://drupal-8.localhost/sites/simpletest/browser_output/Drupal_Tests_rules_Functional_UiPageTest-13-411368.html
http://drupal-8.localhost/sites/simpletest/browser_output/Drupal_Tests_rules_Functional_UiPageTest-14-411368.html
http://drupal-8.localhost/sites/simpletest/browser_output/Drupal_Tests_rules_Functional_UiPageTest-15-411368.html

   ¡Sí, nuestra prueba ha pasado! También generó un par de archivos de salida HTML, donde puede ver las páginas que visitó el navegador durante la prueba. Las páginas se escriben en archivos vinculados como, por ejemplo, arriba, por lo que puede inspeccionarlos después de que se haya ejecutado la prueba.

   Una prueba de demostración fallida

      Realmente no nos enseña mucho tener un examen que tenga éxito. Veamos uno que falla.

     Modificaremos la prueba para provocar una falla en la prueba; afirmaremos que la página contiene un texto diferente, que por supuesto no está allí.

  /**
   * Tests that the reaction rule listing page works.
   */
  public function testReactionRulePage() {
    $account = $this->drupalCreateUser(['administer rules']);
    $this->drupalLogin($account);

    $this->drupalGet('admin/config/workflow/rules');
    $this->assertSession()->statusCodeEquals(200);

    $this->assertSession()->pageTextContains('some text not actually on the page');
  }

   Ejecute la prueba y vea el resultado:

../vendor/bin/phpunit --filter testReactionRulePage ../modules/rules/tests/src/Functional/UiPageTest.php 
PHPUnit 4.8.11 by Sebastian Bergmann and contributors.

E

Time: 24.38 seconds, Memory: 6.00Mb

There was 1 error:

1) Drupal\Tests\rules\Functional\UiPageTest::testReactionRulePage
Behat\Mink\Exception\ResponseTextException: The text "some text not actually on the page" was not found anywhere in the text of the current page.

/home/klausi/workspace/drupal-8/vendor/behat/mink/src/WebAssert.php:787
/home/klausi/workspace/drupal-8/vendor/behat/mink/src/WebAssert.php:262
/home/klausi/workspace/drupal-8/modules/rules/tests/src/Functional/UiPageTest.php:37

FAILURES!
Tests: 1, Assertions: 5, Errors: 1.

HTML output was generated
http://drupal-8.localhost/sites/simpletest/browser_output/Drupal_Tests_rules_Functional_UiPageTest-16-425496.html
http://drupal-8.localhost/sites/simpletest/browser_output/Drupal_Tests_rules_Functional_UiPageTest-17-425496.html
http://drupal-8.localhost/sites/simpletest/browser_output/Drupal_Tests_rules_Functional_UiPageTest-18-425496.html

     Vaya, algo salió mal y nuestra prueba lo detectó. ¡Hurra!

   Cuándo usar t () en las pruebas del navegador

       ¡Nunca! No, ni en los mensajes de afirmación, ni para las etiquetas de los botones, ni para el texto que afirma en la página. Siempre desea probar la cadena literal en la página, no desea probar el sistema de traducción de Drupal.

   Depurar las pruebas del navegador

      Como ya se mencionó, es muy importante habilitar la salida de depuración en la prueba del navegador para que pueda ver las páginas visitadas por el navegador. Si una prueba falla, PHPUnit detiene la ejecución de la prueba donde falla, lo que significa que el último de los enlaces de salida HTML es la página donde ocurrió el error.

     También puede utilizar la función dump () (proporcionada por el componente Symfony VarDumper), ya sea en su método de prueba o en el código del sitio que se está ejecutando. Esto tiene la ventaja sobre el uso de print o print_r () que su salida no se detecta como un error de prueba.

     También puede usar el método ddm () del módulo Devel para generar un archivo de registro. Configure la ubicación del archivo de registro en su código de prueba de esta manera:

    $config = $this->config('devel.settings');
    $config->set('debug_logfile', '/path/to/file/drupal_debug.txt')->save();
    $config->set('debug_pre', FALSE)->save();

   Escribir una clase base para ampliarla con otras clases de prueba.

      Puede encontrar que algunos de sus códigos de prueba duplican la funcionalidad compartida. En este caso, es posible que desee crear una clase base que luego se amplíe con sus diversas clases de prueba. Al hacer esto, hay 2 cosas importantes a tener en cuenta.

  1. La clase de prueba base debe ser abstracta
  2. La clase de prueba base debe terminar con la palabra Base

     Ejemplo de Clase de prueba base: my_module/Tests/src/Functional/myModuleTestBase.php

<?php

namespace Drupal\Tests\my_module\Functional;

/**
 * To be extended by other test classes.
 */
abstract class myModuleTestBase extends BrowserTestBase {
  protected function setUp() {
    parent::setUp();
    // etc
  }
}

     Ampliando la clase base: my_module/Tests/src/Functional/myModuleSomethingTest.php

<?php

namespace Drupal\Tests\my_module\Functional;

/**
 * Test something about my module.
 *
 * @group my_module
 */
class myModuleSomethingTest extends myModuleTestBase {

  protected function setUp() {
    parent::setUp();
  }

  public function testSomething() {
    // do the test.
  }
}

   SQLite

     El uso de una base de datos SQLite hace que las pruebas sean más rápidas que con un DBMS como MySQL. Porque la base de datos está contenida en solo 1 archivo. Asegúrese de que Drupal no esté usando su conexión de base de datos predeterminada desde el archivo settings.php.

     Una solución sencilla es comprobar el HTTP_USER_AGENT. Ej.:

if ($_SERVER['HTTP_USER_AGENT'] !== 'Drupal command line') {

  $databases['default']['default'] = [
    'database' => 'MY-DATABASE',
    'username' => 'root',
    'password' => 'root',
    'prefix' => '',
    'host' => '127.0.0.1',
    'port' => '',
    'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
    'driver' => 'mysql',
    'unix_socket' => '/Applications/MAMP/tmp/mysql/mysql.sock',
  ];

}

     En su archivo phpunit.xml especifique su conexión de base de datos SQLite (el archivo no debe existir antes):

<env name="SIMPLETEST_DB" value="sqlite://localhost/sites/default/files/db.sqlite"/>

     De lo contrario, obtendrá errores como este:

Exception: PDOException: SQLSTATE[HY000] [2002] Connection refused

   Usar datos de sesión

     Puede inspeccionar los datos de la sesión para la solicitud más reciente de la siguiente manera:

    $session_data = $this->container->get('session_handler.write_safe')->read($this->getSession()->getCookie($this->getSessionName()));
    $session_data = unserialize(explode('_sf2_meta|', $session_data)[1]);

Fuente: https://www.drupal.org/docs/automated-testing/phpunit-in-drupal/phpunit-browser-test-tutorial