D8 Automatic Tests - pierregermain/MyDrupal GitHub Wiki

Introducción

Tipos de Tests

  • Tests unitarios. Realizan comprobaciones a nivel de clases. PHPUnit
  • Tests funcionales. Realizan comprobaciones de funcionalidad a un nivel más alto, simulando la carga del sitio en el navegador.

Enlaces Relacionados

Automated tests: https://api.drupal.org/api/drupal/core!core.api.php/group/testing/8.2.x

PHPUnit in Drupal 8: https://www.drupal.org/docs/8/phpunit

Configuration

Crear directorios

mkdir -p web/sites/default/files/simpletest 
chmod 777 web/sites/default/files/simpletest

Configurar phpunit.xml

Pasos:

cd web/core
cp phpunit.xml.dist phpunit.xml
vim phpunit.xml
adaptar la linea <env name="SIMPLETEST_BASE_URL" value="http://docker.test"/>
adaptar la linea <env name="SIMPLETEST_DB" value="mysql://db_user:db_password@database_server_name/my_db_name"/>
adaptar la linea <env name="BROWSERTEST_OUTPUT_DIRECTORY" value="/var/www/sites/default/files/simpletest"/>

Instalar ejecutable de phpunit

Instalar phpunit tal cómo dicen en la página oficial de phpunit.de, pero luego hay que instalar dependiencias para que funcione en Drupal

composer require --dev phpunit/phpunit ^9
composer require --dev symfony/phpunit-bridge
composer require --dev behat/mink-goutte-driver
composer require --dev phpspec/prophecy-phpunit:^2

Ejecución de tests

Módulo Testing

  • Permite ejecutar los tests desde la consola y UI.
  • Se necesita tener dependencias instaladas.

Pasos:

  • Si ya tienes un proyecto montado sin estas dependencias:
    • borra /vendor
    • borra el .lock file
    • composer require --dev 'drupal/core-dev:^8.9'
  • Activar el módulo

Ejecución de tests

Desde la UI

  • Listado de test:
    • Ruta UI: /admin/config/development/testing
  • Desde el listado podemos ejecutar
    • tests tipo PHPUnit
  • Nota: xdebug debe estar desactivado

Desde la consola

Listado

cd web
php core/scripts/run-tests.sh --list

Ejecución

cd web
php core/scripts/run-tests.sh --class 'Drupal\Tests\block\Kernel\BlockStorageUnitTest'

Esto nos devuelve:

Drupal test run
---------------

Tests to be run:
  - Drupal\Tests\block\Kernel\BlockStorageUnitTest

Test run started:
  Monday, March 1, 2021 - 10:47

Test summary
------------

Drupal\Tests\block\Kernel\BlockStorageUnitTest                 2 passes                                      

Test run duration: 3 sec

Test Unitarios (PHPUnit)

  • namespace de los test unitarios con phpunit: Drupal\Tests\mymodule\Unit
  • cada test extiende a UnitTestCase
    • que a su vez extiende a PHPUnit_Framework_TestCase en /vendor/phpunit/phpunit/src/Framework/TestCase.php
  • cada clase tipo test llevará el sufijo Test

Ejemplo de Rutas:

/src/Plugin/Fipsum/LoremIpsum.php
tendrá su Test
/tests/src/Unit/LoremIpsumTest.php
con 
namespace Drupal\Tests\forcontu_plugins\Unit;

Ejemplo

  • Clase a testear: /core/lib/Drupal/Component/Serialization/Json.php

  • Clase test: /core/tests/Drupal/Tests/Component/Serialization/JsonTest.php

La clase test del ejemplo tiene los siguientes elementos:

  • Annotation:
    • @coversDefaultClass: indica la clase a ser testeada
    • @group: indica al grupo que pertenece (así podemos testear varias clases de un mismo grupo a la vez)
  • Método setUp()
    • Se utiliza, por ejemplo, para definir variables (propiedades de la clase), que posteriormente se usarán en los métodos de pruebas.
    • Dentro del método setup() se debe llamar siempre al método de la clase padre: parent::setUp();
  • Métodos test*()
  • Métodos assert*(): determinan si la clase evaluada supera la prueba o no:
    • Ejemplos:
      • assertTrue($condition, $message)
      • assertFalse($condition, $message)
      • assertEquals($expected, $actual, $message) para ver si 2 variables son iguales
      • assertSame($expected, $actual, $message) para ver si 2 variables son iguales en tipo y valor
      • assertGreaterThan(), assertGreaterThanOrEqual(), assertLessThan() y assertLessThanOrEqual()
      • assertDirectoryExists(), assertDirectoryIsReadable(), assertDirectoryIsWritable()
      • assertFileEquals(), assertFileExists(), assertFileIsReadable() y assertFileIsWritable().
      • assertContains(), assertNotContains().
  • Listado de asserts: https://phpunit.de/manual/current/en/appendixes.assertions.html
$this->assertSame($this->string, $json_decoded, 'Encoding a string to JSON and decoding back results in the original string.');

Ejemplo completo en LoremIpsumTest.php

Objetos Mock

Ejemplo:

protected function setUp(): void {
  parent::setUp();
  $this->kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
  $this->banManager = $this->getMock('Drupal\ban\BanIpManagerInterface');
  $this->banMiddleware = new BanMiddleware($this->kernel, $this->banManager);
}

Luego indicamos que para el método 'doSomething' (method()), devuelva el valor 'foo' (willReturn()). Esto implica que, al hacer una llamada a $this->bar->doSomething(), el valor devuelto será 'foo'.

Esta es la forma de testear clases que dependen de objetos de otras clases, evitando dependencias que puedan romper la ejecución del test o dar resultados inesperados.

TODO: Ampliar más sobre esto si hace falta

Test Funcionales (PHPUnit)

Reference: https://deninet.com/blog/2019/01/13/writing-automated-tests-drupal-8-part-2-functional-tests

Primero creamos el sistema de directorios:

modules/custom/my_module
├── mymodule.info.yml
├── mymodule.module
├── src/
└── tests/
    └── src/
        └── Functional/

Para crear un test lo metemos en la siguiente carpeta:

  • tests/src/Functional/NombreDelTestTest.php

El contenido sería:

<?php

namespace Drupal\Tests\mymodule\Functional;

use Drupal\Tests\BrowserTestBase;

/**
 * Test the module settings page
 *
 * @group mymodule
 */
class SettingsPageTest extends BrowserTestBase {

  /**
   * The modules to load to run the test.
   *
   * @var array
   */
  public static $modules = [
    'user',
    'mymodule',
  ];

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

}

Consideraciones:

  • Usamos en los Annotations la directiva @group para agrupar tests similares

  • Para que la clase pueda hacer tests debe ser extendida: \Drupal\Tests\BrowserTestBase

  • Al usar BrowserTestBase debemos implementar, o al menos definir $modules y setUp()

    • $modules se usa para activar módulos necesarios a la hora de realizar un test
  • Por conveniencia la clase test usa el sufijo Test

    • Ejemplo: /mymodule/tests/src/Funtional/FooAddTest.php
  • Los métodos de cada test no tienen argumentos y empiezan por la palabra test:

    • Ejemplo: testFooAddBar()
  • Api: https://api.drupal.org/api/drupal/core!tests!Drupal!Tests!BrowserTestBase.php/class/BrowserTestBase/8

Métodos assert*()

Ejemplos:

# Ejemplo 1
$assert = $this->assertSession()->pageTextContains('foo text');

# Ejemplo 2
$assert = $this->assertSession();
$assert->pageTextContains('foo text');

Ejemplo completo: /modules/contrib/examples/cron_example/tests/src/Functional/CronExampleTest.php https://git.drupalcode.org/project/examples/-/blob/3.x/modules/cron_example/tests/src/Functional/CronExampleTest.php

Explicación del código:

  1. $modules contiene los módulos a ser instalados para realizar el test
  public static $modules = ['cron_example', 'node'];
  1. Dentro del setUp() se crea un nuevo usuario con sus permisos
  public function setUp(): void {
    parent::setUp();
    // Create user. Search content permission granted for the search block to
    // be shown.
    $this->drupalLogin($this->drupalCreateUser(['administer site configuration', 'access content']));
  }

  1. Se utiliza el método assertSession() para acceder a comprobaciones assert adicionales.
    $assert = $this->assertSession();
  1. Cargamos la página dado el route
    $cron_form = Url::fromRoute('cron_example.description');
  1. Obtener la página
    $this->drupalGet($cron_form);
  1. Se envía el formulario
    $this->drupalPostForm(NULL, $post, 'Run cron now');

  1. Miramos si hay el siguiente string
    $assert->pageTextContains('cron_example executed at');
  1. Comprobación si hay texto con expresión regular
    $assert->responseMatches('/Queue 2 worker processed item with sequence 100 /');

Mas info (tutorial) en https://www.drupal.org/node/2783189

Test Funcionales (Behat)

...