0% ont trouvé ce document utile (0 vote)
3 vues42 pages

Introduction aux tests automatisés dans Drupal

Transféré par

olivierh65
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
3 vues42 pages

Introduction aux tests automatisés dans Drupal

Transféré par

olivierh65
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Machine Translated by Google

17
Tests automatisés
Les tests automatisés sont un processus dans lequel nous nous appuyons sur un logiciel spécial pour
exécuter en continu des tests prédéfinis qui vérifient l'intégrité de notre application. À cette fin, les tests
automatisés sont des ensembles d'étapes qui couvrent les fonctionnalités d'une application et comparent les
résultats déclenchés à ceux attendus.

Les tests manuels sont un excellent moyen de garantir qu’une fonctionnalité écrite fonctionne comme
prévu. Le principal problème rencontré par la plupart des adeptes de cette stratégie, notamment ceux qui
l’utilisent exclusivement, est la régression. Une fois qu'une fonctionnalité est testée, la seule façon de garantir
que des régressions (ou des bugs) n'ont pas été introduits par une autre fonctionnalité est de la tester à
nouveau. Et à mesure que l’application grandit, cela devient impossible à gérer. C'est là qu'interviennent
les tests automatisés.

Les tests automatisés utilisent un logiciel spécial doté d'une API qui nous permet d'automatiser les étapes
impliquées dans la fonctionnalité de test. Cela signifie que nous pouvons compter sur des machines pour
exécuter ces tests autant de fois que nous le souhaitons, et la seule chose qui nous empêche d'avoir une
application pleinement fonctionnelle est le manque de couverture de test appropriée avec des tests bien définis.

Il existe de nombreux logiciels différents disponibles pour effectuer de tels tests et ils sont généralement destinés
à des types spécifiques de tests automatisés. Par exemple, Behat est un puissant framework de tests de
comportement open source basé sur PHP qui permet de créer des scripts de tests qui reflètent assez fidèlement
ce qu'un testeur manuel ferait : interagir avec l'application via le navigateur et tester son comportement.
Machine Translated by Google

542 Tests automatisés

Il existe d'autres frameworks de tests dont le niveau de leur objectif de test est beaucoup plus bas.
Par exemple, l'outil standard de l'industrie PHP, PHPUnit, est largement utilisé pour effectuer des tests unitaires. Ce type
de test se concentre sur le code réel au niveau le plus bas possible ; il teste si les méthodes de classe fonctionnent
correctement en vérifiant leur sortie après leur avoir fourni des entrées différentes. Un argument fort en faveur de ce type
de tests est qu’ils encouragent une meilleure architecture de code, qui peut être (en partie) mesurée par la facilité
avec laquelle les tests unitaires peuvent être écrits pour celle­ci.

Nous avons également des tests fonctionnels ou d'intégration, qui se situent quelque part entre ces deux exemples.
Ceux­ci vont au­delà du niveau du code et font appel à des sous­systèmes d'application afin de tester des ensembles
de fonctionnalités plus complets, sans nécessairement prendre en compte le comportement du navigateur et l'interaction
de l'utilisateur.

Il n’est pas difficile de convenir qu’une application bien testée présente une combinaison de différentes méthodologies de
test. Par exemple, tester les unités architecturales individuelles d'une application ne garantit pas que l'ensemble
du sous­système fonctionne, tout comme tester uniquement le sous­système ne garantit pas que ses composants
individuels fonctionneront correctement en toutes circonstances. Il en va de même pour certains sous­systèmes qui
dépendent de l'interaction de l'utilisateur : ceux­ci nécessitent également une couverture de test.

Dans ce chapitre, nous verrons comment fonctionnent les tests automatisés dans Drupal. Plus précisément, nous
passerons en revue et expliquerons toutes les méthodologies de test disponibles pour nous en tant que développeurs
de modules et leur fournirons des exemples avec deux tests chacun. À la fin de ce chapitre, vous serez prêt à écrire vos
propres tests et serez suffisamment familier avec le code pour explorer davantage les fonctionnalités de test
disponibles.

Les principaux sujets que nous aborderons dans ce chapitre sont les suivants :

• Méthodologies de test dans Drupal 9

• Enregistrement des tests

• Se familiariser avec les tests unitaires, noyaux, fonctionnels et FunctionalJavaScript.

Méthodologies de test dans Drupal 9


Les tests PHP de Drupal sont tous exécutés par PHPUnit, qui couvre plus de méthodologies de test que celles mentionnées
précédemment. Alors, voyons de quoi il s'agit.

Drupal 9 est livré avec les types de tests PHP suivants :

• Unit : tests de classe de bas niveau avec des dépendances minimales (généralement simulés)

• Kernel : Tests fonctionnels avec le noyau bootstrapé, accès à la base de données,


et seulement quelques modules chargés
Machine Translated by Google

PHPUnit 543

• Fonctionnel : tests fonctionnels avec une instance Drupal amorcée, quelques modules installés et à l'aide d'un
émulateur de navigateur basé sur Mink (pilote Goutte)

• JavaScript fonctionnel : Tests fonctionnels utilisant le driver Selenium pour Mink,


permettant le test des fonctionnalités basées sur JavaScript

Comme mentionné, toutes ces suites de tests sont construites sur PHPUnit et sont donc exécutées par celui­ci.
En fonction de l'espace de noms dans lequel résident les classes de test, ainsi que de l'emplacement du
répertoire, Drupal peut découvrir ces tests et savoir de quel type ils sont.

Dans ce chapitre, nous verrons des exemples de chacun d'eux au fur et à mesure que nous testerons
certaines des fonctionnalités que nous avons écrites dans ce livre.

PHPUnit
Drupal utilise PHPUnit comme framework de test pour tous les types de tests. Dans cette section, nous verrons
comment nous pouvons l'utiliser pour exécuter des tests.

Note importante:
Dans votre environnement de développement (ou partout où vous souhaitez exécuter les
tests), assurez­vous que les dépendances du composer sont installées avec l'option ­­dev
drapeau. Cela inclura PHPUnit. N'oubliez pas de ne pas faire cela dans votre
environnement de production car vous pourriez compromettre la sécurité de votre application.

Bien que Drupal dispose d'une interface utilisateur pour exécuter des tests, PHPUnit n'y est pas bien intégré.
Il est donc recommandé d’exécuter les tests en utilisant plutôt la ligne de commande. En fait, c'est très simple à
faire. Pour exécuter une suite de tests entière (d'un certain type), nous devons accéder au dossier principal de
Drupal (cela fonctionne dans une installation normale de site Drupal où se trouve le dossier du fournisseur) :

noyau de cd

Et exécutez la commande suivante :

../vendor/bin/phpunit ­­testsuite=unité

Cette commande remonte un dossier dans le répertoire du fournisseur et utilise l' exécutable phpunit installé .
Machine Translated by Google

544 Tests automatisés

Si vous suivez le référentiel GitHub qui accompagne ce livre, le dossier du fournisseur est placé ailleurs et il existe un
fichier de configuration PHPUnit à la racine du projet. Ainsi, pour obtenir la même chose que ce que nous avons fait ici,
vous devez exécuter à partir du dossier racine :

./vendor/bin/phpunit ­­testsuite=unité

À l’avenir, nous supposerons une installation Drupal standard où le fichier de configuration PHPUnit se trouve
dans le dossier principal .

En option, dans l’exemple précédent, nous avons précisé que nous souhaitons uniquement exécuter des tests unitaires.
Omettre cela exécuterait tous les types de tests. Cependant, pour la plupart des autres, une certaine configuration sera
nécessaire, comme nous le verrons dans les sections respectives. Si nous voulons exécuter un test spécifique, nous
pouvons le passer en argument à la commande phpunit (le chemin d'accès au fichier) :

../vendor/bin/phpunit tests/Drupal/Tests/Core/Routing/
[Link]

Dans cet exemple, nous exécutons un test principal Drupal qui teste la classe UrlGenerator .
Alternativement, nous pouvons exécuter plusieurs tests appartenant au même groupe (nous verrons bientôt
comment les tests sont ajoutés à un groupe) :

../vendor/bin/phpunit ­­group=Routage

Cela exécute tous les tests du groupe Routing , qui contient en fait le UrlGeneratorTest que nous avons
vu précédemment. Nous pouvons exécuter des tests à partir de plusieurs groupes si nous les séparons par
des virgules.

De plus, pour vérifier quels sont les groupes disponibles, nous pouvons exécuter la commande suivante :

../vendor/bin/phpunit ­­list­groups

Cela listera tous les groupes qui ont été enregistrés auprès de PHPUnit.

Enfin, nous pouvons également exécuter une méthode spécifique trouvée dans un test en utilisant le –filter
argument:

../vendor/bin/phpunit ­­
filter=testAliasGenerationUsingInterfaceConstants

C'est l'une des méthodes de test du même UrlGeneratorTest que nous avons vu auparavant et c'est la seule qui s'exécuterait.
Machine Translated by Google

Enregistrement des tests 545

Enregistrement des tests


Il existe certains points communs entre les différents types de suites de tests concernant ce que nous devons
faire pour que Drupal (et PHPUnit) puisse les découvrir et les exécuter.

Tout d’abord, nous avons le répertoire où doivent se trouver les classes de test. Le modèle est le suivant : tests/
src/[suite_type], où [suite_type] est le nom du type de suite de tests que ce test devrait être. Il peut s'agir de l'un
des éléments suivants :

• Unité

• Noyau

• Fonctionnel

• Javascript fonctionnel

Ainsi, par exemple, les tests unitaires iraient dans le dossier tests/src/Unit de notre module.

Deuxièmement, les classes de test doivent également respecter une structure d'espace de noms :

espace de noms Drupal\Tests\[module_name]\[suite_type]

C’est également assez simple à comprendre.

Troisièmement, nous devons avoir certaines métadonnées dans la classe de test PHPDoc. Chaque classe
doit avoir une ligne récapitulative décrivant à quoi sert la classe de test. Seules les classes qui utilisent l' attribut
@coversDefaultClass peuvent omettre la ligne récapitulative. De plus, toutes les classes de test doivent avoir
l' annotation @group PHPDoc indiquant le groupe dont elles font partie.
C'est ainsi que PHPUnit peut exécuter des tests appartenant uniquement à certains groupes.

Maintenant que nous savons comment enregistrer et exécuter des tests, examinons les tests unitaires et voyons comment nous
pouvons écrire le nôtre.

Tests unitaires
Comme brièvement mentionné au début du chapitre, les tests unitaires sont utilisés pour tester des unités
uniques qui composent l'architecture du code. En pratique, cela signifie tester des classes individuelles, en
particulier les méthodes qu'elles contiennent et ce qu'elles doivent faire. Étant donné que les tests se
déroulent à un niveau si bas, ils sont de loin les tests les plus rapides pouvant être exécutés.

La logique derrière les tests unitaires est assez simple : après avoir fourni une entrée, le test affirme que
le résultat de la méthode est correct. En règle générale, plus il couvre de scénarios d’entrée ­> sortie, plus le
code testé est stable. Par exemple, les tests doivent également couvrir des scénarios inattendus, ainsi que tester
tout le code contenu dans les méthodes testées (comme les forks créés par les instructions if/else).
Machine Translated by Google

546 Tests automatisés

Le modèle de programmation de l'injection de dépendances (les objets doivent recevoir comme


dépendances d'autres objets dont ils pourraient avoir besoin) devient critique lorsqu'il s'agit de tests unitaires.
La raison en est que si les méthodes de classe fonctionnent avec la portée globale ou instancient d'autres objets,
nous ne pouvons plus les tester proprement. Au lieu de cela, s'ils nécessitent des dépendances, nous pouvons
nous en moquer et les transmettre dans le contexte des tests exécutés. Nous en verrons quelques exemples
prochainement. Mais avant de faire cela, créons une classe simple qui peut être facilement testée à l'aide d'un test
unitaire.

Un exemple typique est une simple classe de calculatrice. Il prendra deux nombres comme arguments pour son
constructeur et disposera de quatre méthodes pour effectuer des opérations arithmétiques de base sur ces nombres.
Nous allons mettre cela dans notre module Hello World :

espace de noms Drupal\hello_world ;

/**
* Classe utilisée pour démontrer un test unitaire simple. */

Calculatrice de classe {

$a protégé ;
protégé $b ;

fonction publique __construct($a, $b) { $this­>a = $a;

$this­>b = $b;
}

fonction publique ajouter() {


return $this­>a + $this­>b;
}

fonction publique soustraire() {


return $this­>a ­ $this­>b;
}

fonction publique multiplier() {


return $this­>a * $this­>b;
Machine Translated by Google

Tests unitaires 547

fonction publique diviser() {


return $this­>a / $this­>b;
}
}

Rien de bien compliqué ici. Vous pourriez affirmer qu’une classe de calculatrice ne devrait avoir aucune
dépendance mais plutôt transmettre les nombres aux méthodes arithmétiques réelles. Cependant, cela
fonctionnera très bien pour notre exemple et est un peu moins répétitif.

Créons maintenant le premier test unitaire pour nous assurer que cette classe se comporte comme prévu.
Dans la section précédente, nous avons vu dans quel répertoire ils doivent aller. Donc, dans notre cas,
ce sera /tests/src/Unit. Et la classe de test ressemble à ceci :

espace de noms Drupal\Tests\hello_world\Unit ;

utilisez Drupal\hello_world\Calculator ; utilisez


Drupal\Tests\UnitTestCase ;

/**
* Teste les méthodes de la classe Calculatrice.
*
*
@group hello_world */

la classe CalculatorTest étend UnitTestCase {

/**
* Teste la méthode Calculator::add().
*/
public function testAdd() { $calculatrice =
nouvelle Calculatrice(10, 5);
$this­>assertEquals(15, $calculator­>add());
}

/** *
Teste la méthode Calculator::subtract().
*/
Machine Translated by Google

548 Tests automatisés

fonction publique testSubtract() {


$calculatrice = nouvelle Calculatrice(10, 5);
$this­>assertEquals(5, $calculator­>subtract());
}

/**
* Teste la méthode Calculator::multiply(). */

fonction publique testMultiply() {


$calculatrice = nouvelle Calculatrice(10, 5);
$this­>assertEquals(50, $calculator­>multiply());
}

/**
* Teste la méthode Calculator::divide().
*/
fonction publique testDivide() {
$calculatrice = nouvelle Calculatrice(10, 5);
$this­>assertEquals(2, $calculator­>divide());
}

Tout d’abord, vous remarquez que l’espace de noms correspond au modèle que nous avons vu
dans la section précédente. Deuxièmement, le PHPDoc contient les informations requises : un
résumé et la balise @group . Troisièmement, le nom de la classe se termine par le mot Test.
Enfin, la classe étend UnitTestCase, qui est la classe de base que nous devons étendre pour tous
les tests unitaires.

Note importante:
Tous les types de noms de classes de test dans Drupal doivent se terminer par le mot
Test et étendre la classe de base appropriée qui fournit un code spécifique pour ce type de test.

Ensuite, nous avons les méthodes réelles qui testent divers aspects de la classe Calculator et qui
doivent toujours commencer par le mot test. C'est ce qui indique à PHPUnit qu'ils doivent être exécutés.
Ces méthodes sont elles­mêmes des tests autonomes, ce qui signifie que la classe CalculatorTest
comporte quatre tests. De plus, chacun de ces tests s’exécute indépendamment des autres.
Machine Translated by Google

Tests unitaires 549

Puisque l' arithmétique de la calculatrice est très simple, il n'est pas difficile de comprendre ce que nous
faisons pour la tester. Pour chaque méthode, nous instancions une nouvelle instance avec quelques nombres,
puis nous affirmons que le résultat de l'opération arithmétique est égal à ce que nous attendons. La classe de base
fournit une multitude de méthodes d'assertion différentes que nous pouvons utiliser dans nos tests. Comme
ils sont très nombreux, nous n’allons pas tous les aborder ici. Nous en verrons davantage au fur et à mesure
que nous écrirons d'autres tests, mais je vous recommande fortement de vérifier les classes de base des
différents types de suites de tests pour les méthodes commençant par le mot assert. Un excellent moyen de
procéder consiste également à utiliser un IDE qui se complète automatiquement lorsque vous tapez le nom de
la méthode. Cela peut être très pratique.

Avec cela, nous pouvons déjà exécuter le test et voir s’il réussit. Normalement, cela devrait être le cas, car
nous pouvons faire des calculs dans notre tête et nous savons que c'est correct :

../vendor/bin/phpunit ../modules/custom/hello_world/tests/src/
Unité/[Link]

Le résultat devrait être vert :

OK (4 tests, 4 assertions)

Cependant, j’ai mentionné plus tôt qu’un bon test tient également compte des situations inattendues et des
réponses négatives. Nous ne l’avons pas très bien fait dans notre exemple. Si nous regardons testAdd(),
nous pouvons voir que l’assertion est correcte avec ces deux nombres. Mais que se passe­t­il si nous passons
plus tard à la méthode Calculator::add() et la modifions par accident :

retour 15 ;

Le test réussira quand même, mais sera­t­il réellement positif ? Pas vraiment, car si on passe des nombres
différents, le calcul ne correspondra plus. Nous devrions donc tester ces méthodes avec plus d’un simple ensemble
de nombres pour prouver réellement que les mathématiques derrière la classe Calculatrice sont valides.

Donc, à la place, nous pouvons faire quelque chose comme ceci :

$calculatrice = nouvelle Calculatrice(10, 5);


$this­>assertEquals(15, $calculator­>add());
$calculatrice = nouvelle Calculatrice(10, 6);
$this­>assertEquals(16, $calculator­>add());

De cette façon, nous sommes sûrs que l’opération d’addition fonctionne correctement. Un compromis est que
nous avons un peu de code répétitif, surtout si nous devons également le faire pour toutes les autres
opérations.
Machine Translated by Google

550 tests automatisés

Généralement, lors de l’écriture de tests, la répétition est beaucoup plus acceptable que lors de l’écriture du
code lui­même. Souvent, vous ne pouvez rien y faire car le code semblera très répétitif. Cependant, dans
notre cas, nous pouvons réellement faire quelque chose en utilisant setUp()
méthode, qui est appelée par PHPUnit avant l'exécution de chaque méthode de test. Son objectif est
d'effectuer diverses tâches de préparation communes à tous les tests du cours. Cependant, cela ne signifie pas
qu’il ne s’exécute qu’une seule fois et qu’il est ensuite utilisé par tous. En fait, il s'exécute avant chaque méthode
de test individuelle.

Donc, ce que nous pouvons faire ressemble à ceci :

/** *

@var \Drupal\hello_world\Calculator */

protégé $calculatorOne ;

/** *

@var \Drupal\hello_world\Calculator */

protégé $calculatorTwo ;

/** *
{@inheritdoc} */

fonction protégée setUp() {


parent::setUp(); $this­
>calculatorOne = new Calculator(10, 5);
$this­>calculatorTwo = new Calculator(10, 2);
}

Nous créons deux propriétés de classe et dans la méthode setUp() , nous leur attribuons nos objets
calculatrice. Une chose très importante à garder à l'esprit est de toujours appeler le parent de cette méthode
car elle effectue des choses très importantes pour la configuration de l'environnement, en particulier lorsque
nous passons aux tests du noyau et des fonctionnalités.

Maintenant, la méthode testAdd() peut ressembler à ceci :

fonction publique testAdd() {


$this­>assertEquals(15, $this­>calculatorOne­>add());
$this­>assertEquals(12, $this­>calculatorTwo­>add());
}
Machine Translated by Google

Tests unitaires 551

Beaucoup plus propre et moins répétitif. Sur cette base, vous pouvez extrapoler et appliquer vous­même les
mêmes modifications aux autres méthodes.

Dépendances simulées
Les classes testées sont rarement aussi simples que notre classe de calculatrice. La plupart du temps, ils auront
des dépendances qui, à leur tour, auront également des dépendances. Les tests unitaires deviennent donc
un peu plus compliqués. En fait, la facilité avec laquelle les tests unitaires sont écrits est devenue un test décisif
pour la qualité du code testé : moins le test unitaire est compliqué, meilleur est le code.

Comme deuxième exemple d'écriture de tests unitaires, entrons dans le « monde réel » et testons l'une des
classes que nous avons écrites dans ce livre, à savoir la classe UserTypesAccess . Si vous vous souvenez du
chapitre 10, Contrôle d'accès, nous avons créé ce service pour être utilisé sur les itinéraires comme vérificateur
d'accès. Bien que nous puissions écrire des tests fonctionnels qui vérifient qu'il fonctionne bien dans le cadre
du système d'accès, nous pouvons également écrire un test unitaire pour vérifier le code réel dans access ().
méthode. Alors, commençons.

La première chose que nous devons faire est de créer la classe (en respectant l'emplacement du répertoire ainsi
que l'espace de noms de la classe) :

espace de noms Drupal\Tests\user_types\Unit ;

utilisez Drupal\Tests\UnitTestCase ;

/**
* Teste les méthodes de la classe UserTypesAccess.
*
*
@group user_types */

la classe UserTypesAccessTest étend UnitTestCase {}

Jusqu'à présent, les choses ressemblent à notre exemple précédent : nous avons les informations PHPDoc et
nous étendons la classe UnitTestCase . Écrivons donc un test pour la méthode access() de la classe
UserTypesAccess . Cependant, si vous vous en souvenez, cette méthode prend deux arguments (un
compte utilisateur et un objet route) et utilise également le gestionnaire de types d'entités, qui est injecté dans la
classe. C’est donc là que réside l’essentiel de la complexité. Ce que nous devons tester, c'est la valeur de
retour de la méthode en fonction de ces arguments : essentiellement, si elle autorisera ou refusera l'accès si le
compte utilisateur a certaines valeurs trouvées sur la route.
Machine Translated by Google

552 Tests automatisés

Dans les tests unitaires, les dépendances sont généralement moquées. Cela signifie que PHPUnit
créera des objets similaires vides qui se comporteront comme nous les prescrivons et que nous
pourrons les utiliser comme dépendances. La façon de créer un objet fictif simple est la suivante :

$user = $this­>createMock('Drupal\user\Entity\User');

L' objet $user sera désormais une simulation de la classe d'entité Drupal User . Bien sûr, cela ne fera rien,
mais il peut être utilisé comme dépendance. Pour le rendre réellement utile, nous devons lui prescrire un
comportement en fonction de ce que le code testé en fait. Par exemple, s’il appelle sa méthode id() , nous
devons prescrire ce comportement. Nous pouvons le faire avec des attentes :

$user­>attend($this­>any())
­>méthode('id')
­>will($this­>returnValue(1));

Cela indique à l'objet fictif que pour chaque appel à la méthode id() , il doit renvoyer la valeur 1. La méthode
expects() prend en compte un matcher, qui peut être encore plus restrictif.
Par exemple, au lieu de $this­>any(), nous pouvons utiliser $this­>once(), ce qui signifie que la méthode id()
de l'objet fictif ne peut être appelée qu'une seule fois. Consultez la classe de base pour les autres options
disponibles, ainsi que ce que vous pouvez transmettre à la méthode will() :
bien que $this­>returnValue() soit le plus courant. Enfin, si la méthode id() prend un argument, on peut aussi avoir
la méthode with() à laquelle on passe la valeur de l'argument attendu dans le matcher.

Une manière plus complexe de créer une simulation consiste à utiliser le générateur de simulation :

$user = $this­>getMockBuilder('Drupal\user\Entity\User') ­>getMock();

Cela obtiendra le même objet fictif mais permettra quelques options supplémentaires dans sa construction.
Je vous recommande de consulter la documentation PHPUnit pour plus d'informations, car elle va aussi loin
que nous allons l'approfondir dans ce livre sur les objets moqueurs.

Maintenant que nous en savons un peu plus sur la moquerie, nous pouvons procéder à la rédaction de notre
test. Pour ce faire, nous devons réfléchir à l’objectif final et revenir à tous les appels de méthode dont nous
devons nous moquer. Pour rappel, voici le code que nous devons tester :

accès aux fonctions publiques (AccountInterface $account, Route $route) {

$user_types = $route­>getOption('_user_types'); si ($user_types) {

return AccessResult::interdit();
Machine Translated by Google

Tests unitaires 553

} if ($compte­>isAnonymous()) {
return AccessResult::interdit();

} $user = $this­>entityTypeManager­>getStorage('user')­
>load($compte­>id()); $type =
$user­>get('field_user_type')­>value; return in_array($type, $user_types) ?
AccessResult :: autorisé ()
: AccessResult::interdit(); }

Ainsi, à première vue, nous voyons que nous devons nous moquer d'EntityTypeManager. Les
arguments de la méthode que nous instancierons manuellement avec des données factices à l'intérieur.
Cependant, se moquer d'EntityTypeManager va être assez compliqué. Un appel à son getStorage()
La méthode doit renvoyer un objet UserStorage . Cela doit également être ridiculisé car un appel à sa
méthode load() doit renvoyer un objet d'entité User . Enfin, nous devons également nous moquer de
cela car un appel à sa méthode get() devrait également renvoyer un objet valeur.

Comme je l'ai mentionné, nous procéderons en revenant de notre objectif final. Ainsi, nous pouvons
commencer par instancier les types d’ objets AccountInterface que nous voulons transmettre, ainsi
que les objets route :

/**
* Teste la méthode UserTypesAccess::access().
*/
fonction publique testAccess() {
// Comptes utilisateur
$anonyme = new UserSession(['uid' => 0]);
$registered = new UserSession(['uid' => 2]);

// Définitions d'itinéraires.
$manager_route = nouvelle Route('/test_manager', [], [], ['_user_
types' => ['gestionnaire']]);
$board_route = nouvelle Route('/test_board', [], [], ['_user_
types' => ['carte']]);
$none_route = nouvelle Route('/test_board');
}
Machine Translated by Google

554 Tests automatisés

Et les nouvelles déclarations d'utilisation en haut :

utilisez Drupal\Core\Session\UserSession ; utilisez


Symfony\Component\Routing\Route ;

Fondamentalement, nous voulons tester ce qui se passe pour les deux types d'utilisateurs : anonymes et
enregistrés. Lors de l'instanciation des objets UserSession (qui implémentent AccountInterface), nous
transmettons certaines données à stocker avec eux. Dans notre cas, nous avons besoin de l'uid de l'utilisateur car il sera
demandé par le code testé lors de la vérification si l'utilisateur est anonyme ou non.

Ensuite, nous créons trois routes : une à laquelle les gestionnaires devraient avoir accès, une à laquelle les membres du
conseil d'administration devraient avoir accès et une à laquelle personne ne devrait avoir accès (comme indiqué par l'option
_user_types sur la route ) . Reportez­vous au chapitre 10, Contrôle d'accès, si vous ne vous souvenez pas de l'objet de
cette fonctionnalité.

Une fois cela fait, nous instancions notre classe UserTypesAccess , en vue d'appeler sa méthode access() avec diverses
combinaisons de nos objets account et route :

$access = new UserTypesAccess($entity_type_manager);

Et la nouvelle déclaration d'utilisation en haut :

utilisez Drupal\user_types\Access\UserTypesAccess ;

Cependant, nous n'avons pas encore de gestionnaire de types d'entités, nous devons donc nous en moquer. Voici tout le
code dont nous avons besoin pour simuler le gestionnaire de types d'entités afin qu'il fonctionne pour notre code testé (cela
précède le code que nous avons écrit jusqu'à présent dans ce test) :

// Entité utilisateur simulée.


$type = nouveau \stdClass(); $type­
>value = 'gestionnaire'; $user = $this­
>getMockBuilder('Drupal\user\Entity\User')
­>disableOriginalConstructor() ­>getMock();

$user­>attend($this­>any())
­>method('get')
­>will($this­>returnValue($type));

// Simulation de stockage
utilisateur $user_storage = $this­>getMockBuilder('Drupal\user\
Stockage utilisateur')
Machine Translated by Google

Tests unitaires 555

­>disableOriginalConstructor() ­>getMock();

$user_storage­>expects($this­>any())
­>méthode('charger')
­>will($this­>returnValue($user));

// Simulation du gestionnaire de types d'entités.


$entity_type_manager = $this­>getMockBuilder('Drupal\Core\
Entité\EntityTypeManager')
­> désactiverOriginalConstructor()
­>getMock();
$entity_type_manager­>expects($this­>any())
­>method('getStorage') ­>will($this­
>returnValue($user_storage));

Tout d’abord, vous remarquerez que le gestionnaire de types d’entités n’est moqué qu’à la toute fin.
Nous devons d’abord démarrer la chaîne d’appel, qui se termine par une valeur de champ d’objet d’entité
utilisateur. Ainsi, le premier bloc se moque de l'objet entité User, qui attend un nombre quelconque
d'appels à sa méthode get() à laquelle il renverra toujours un objet stdClass() avec la valeur de propriété
égale à la chaîne du gestionnaire . De cette façon, nous nous moquons de l’accesseur système de champ
d’entité.

Note importante
Tout en utilisant le constructeur de mocks pour créer nos mocks, nous
pouvons utiliser la méthode DisableOriginalConstructor() pour empêcher
PHPUnit d'appeler le constructeur de la classe d'origine. Ceci est important afin
d'éviter le besoin de toutes sortes d'autres dépendances qui n'ont pas réellement
d'impact sur le code testé.

Maintenant que nous avons la maquette de l'entité User, nous pouvons l'utiliser comme valeur de
retour de la méthode load() de la maquette UserStorage . Ceci, à son tour, est la valeur de retour de la
méthode getStorage() du gestionnaire de types d'entités . Ainsi, tout le code que nous avons écrit signifie que
nous nous sommes moqués de la chaîne suivante :

$this­>entityTypeManager­>getStorage('user')­>load($account­
>id());

Peu importe ce que nous transmettons à la méthode load() , car nous aurons toujours cette entité utilisateur
qui a le type d'utilisateur manager .
Machine Translated by Google

556 Tests automatisés

Maintenant que tout est simulé, nous pouvons utiliser l' objet $access que nous avons créé précédemment et faire
des assertions basées sur des appels à sa méthode access() :

// Accès refusé en raison du manque d'option de route.


$this­>assertInstanceOf(\Drupal\Core\Access\
AccessResultForbidden::class, $access­>access($registered, $none_route));

// Accès refusé car l'utilisateur est anonyme sur l'un des


itinéraires

$this­>assertInstanceOf(\Drupal\Core\Access\
AccessResultForbidden::class, $access­>access($anonymous, $manager_route));

$this­>assertInstanceOf(\Drupal\Core\Access\
AccessResultForbidden::class, $access­>access($anonymous, $board_route));

// Accès refusé car l'utilisateur n'a pas la bonne valeur de champ


$this­>assertInstanceOf(\Drupal\Core\Access\
AccessResultForbidden::class, $access­>access($registered, $board_route));

// Accès autorisé car l'utilisateur possède la valeur de champ appropriée.


$this­>assertInstanceOf(\Drupal\Core\Access\
AccessResultAllowed::class, $access­>access($registered, $manager_route));

La valeur de retour est toujours un objet qui implémente une interface (soit AccessResultAllowed,
soit AccessResultForbidden), c'est donc ce que nous devons affirmer. Nous vérifions quatre cas

d'utilisation différents :

• Accès refusé s'il n'y a pas d'option d'itinéraire

• Accès refusé aux utilisateurs anonymes sur l'un des itinéraires

• Accès refusé aux utilisateurs enregistrés avec le mauvais type d'utilisateur

• Accès autorisé aux utilisateurs enregistrés avec le type d'utilisateur approprié

Ainsi, avec cela, nous pouvons exécuter le test et devrions, espérons­le, obtenir un résultat vert :

../vendor/bin/phpunit ../modules/custom/user_types/tests/src/
Unité/[Link]
Machine Translated by Google

Tests du noyau 557

Ce sont les bases de l’écriture de tests unitaires. Il existe de nombreux autres types d'assertions et vous finirez par vous
moquer de nombreuses dépendances dans Drupal. Mais ne vous laissez pas décourager par la lenteur rencontrée au
début, car les choses deviendront plus rapides à mesure que vous gagnerez en expérience.

Tests du noyau
Les tests de noyau sont la méthodologie de test immédiate de niveau supérieur que nous pouvons avoir dans Drupal et
sont en fait des tests d'intégration qui se concentrent sur le test de divers composants. Ils sont plus rapides que les tests
fonctionnels classiques car ils n'effectuent pas une installation complète de Drupal, mais utilisent une pseudo­installation en
mémoire qui est beaucoup plus rapide à démarrer. Pour cette raison, ils ne gèrent pas non plus les interactions du navigateur
et n’installent aucun module automatiquement.

Outre le code lui­même, les tests du noyau fonctionnent également avec la base de données et nous permettent de charger
les modules dont nous avons besoin pour exécuter le test. Cependant, contrairement aux tests fonctionnels que nous
verrons ensuite, les tests du noyau nous obligent également à déclencher manuellement l'installation de tous les schémas
de base de données dont nous avons besoin. Mais nous verrons comment procéder dans les deux exemples que nous
couvrons dans cette section.

Cependant, avant de pouvoir travailler avec les tests du noyau, nous devons nous assurer que nous disposons d'une connexion
à la base de données, et PHPUnit en est conscient. Dans le dossier principal de notre installation Drupal, nous
trouvons un fichier [Link] , que nous devons dupliquer et renommer en [Link]. Il s'agit du fichier de configuration
PHPUnit. Normalement, ce fichier devrait déjà être ignoré par Git, vous n'avez donc pas à vous soucier de le valider
dans le référentiel.

Dans ce fichier, nous trouvons une variable d'environnement appelée SIMPLETEST_DB où nous pouvons spécifier la
connexion à la base de données, en utilisant le format indiqué dans le code commenté suivant :

mysql://nom d'utilisateur:password@localhost/databasename#table_prefix

Une fois cela fait, PHPUnit pourra se connecter à la base de données afin d'installer Drupal pour les tests Kernel ainsi que les
tests Functional et FunctionalJavaScript.

Si vous suivez le référentiel GitHub accompagnant ce livre, ce fichier de configuration se trouve dans le dossier racine
et est déjà préparé pour exécuter tous les types de tests que nous couvrons dans ce livre.

Note importante:
En règle générale, vous devriez toujours opter pour les tests du noyau plutôt que pour les tests
fonctionnels chaque fois que les interactions du navigateur ne sont pas impliquées et que les tests du
noyau suffisent pour faire le travail. En effet, une suite remplie de tests peut prendre beaucoup de
temps à s'exécuter, vous devez donc la rendre aussi performante que possible.
Machine Translated by Google

558 Tests automatisés

Test TeamCleaner
Maintenant que nous avons couvert cela, il est temps d'écrire notre premier test du noyau. Et un exemple
simple et intéressant peut être de tester le plugin de file d'attente TeamCleaner que nous avons créé au chapitre
14, Lots, files d'attente et Cron. Si vous vous demandez pourquoi cela ne peut pas être testé à l'aide de la
méthodologie de test unitaire ultra­rapide, la réponse est que sa méthode unique ne renvoie rien. Au lieu de
cela, il modifie les valeurs de la base de données auxquelles nous devons accéder afin de vérifier que tout
s'est bien passé.

La classe test se place naturellement dans le dossier tests/src/Kernel de notre module et peut démarrer ainsi :

espace de noms Drupal\Tests\sports\Kernel ;

utilisez Drupal\KernelTests\KernelTestBase ;

/**
* Testez le plugin TeamCleaner QueueWorker.
*
* @sports de groupe

*/
la classe TeamCleanerTest étend KernelTestBase {}
L'espace de noms est cohérent avec ceux que nous avons vus jusqu'à présent et nous disposons des
annotations PHPDoc correctes pour enregistrer le test. De plus, cette fois, nous étendons de
KernelTestBase.

La première chose que nous devons faire est de spécifier les modules que nous voulons charger lors de l'exécution
de ce test. Dans notre cas, il s'agit du module sports , nous pouvons donc ajouter une propriété de classe qui
contient ce nom :

/** *
{@inheritdoc} */

$modules statiques protégés = ['sports'];


Machine Translated by Google

Tests du noyau 559

Spécifier une liste de modules ici ne les installe pas réellement mais les charge et les ajoute simplement au
conteneur de services. Alors oui, nous avons accès au module et au code ainsi qu'au conteneur. Mais cela
signifie également que les schémas définis par ces modules ne sont pas réellement créés, nous devons donc le
faire manuellement. Il en va de même pour la configuration avec laquelle le module est livré. Mais nous pouvons
gérer ces choses dans la méthode setUp() ou dans la méthode de test elle­même. Nous opterons pour cette
dernière car, dans ce cas, nous n'avons qu'une seule méthode de test dans la classe. Et le tout peut
ressembler à ceci :

/**
* Teste la méthode TeamCleaner::processItem(). */

fonction publique testProcessItem() {


$this­>installSchema('sports', 'équipes');
$database = $this­>container­>get('base de données'); $fields = ['name' =>
'Nom de l'équipe'];
$id = $base de données­>insert('équipes')
­>champs ($champs)
­>exécuter();

$records = $database­>query("SELECT id FROM {teams} WHERE id = :id", [':id' => $id])­>fetchAll();


$this­>assertNotEmpty($records);

$worker = new TeamCleaner([], NULL, NULL, $database);


$data = nouveau \stdClass();
$données­>id = $id;
$worker­>processItem($data); $records =
$database­>query("SELECT id FROM {teams} WHERE id = :id", [':id' => $id])­>fetchAll();

$this­>assertEmpty($records);
}

Et la déclaration d'utilisation :

utilisez Drupal\sports\Plugin\QueueWorker\TeamCleaner ;
Machine Translated by Google

560 tests automatisés

Puisque le plugin TeamCleaner supprime les équipes, il suffit d'installer uniquement cette table. Nous pouvons le
faire en utilisant la méthode parent installSchema() , à laquelle nous transmettons le nom du module et la table que
nous voulons installer. Nous ne nous occupons pas réellement des joueurs, nous devons donc éviter de faire des
travaux inutiles, comme la création de la table des joueurs .

Ensuite, de la même manière que nous le faisons dans le code réel, nous récupérons le service de base de
données du conteneur et ajoutons un enregistrement à la table des équipes . Ce sera l'enregistrement de test

que nous supprimerons, nous nous souviendrons donc de son $id. Mais avant de tester cela, nous voulons être
absolument sûrs que notre enregistrement a été sauvegardé. Nous le recherchons donc et affirmons que le
résultat n’est pas vide. La méthode assertNotEmpty() est une autre assertion utile que nous pouvons utiliser lorsque
nous traitons de tableaux.

Maintenant que nous sommes certains que l'enregistrement est dans la base de données, nous pouvons le « traiter » à l'aide de notre plugin.

Nous instancions donc un objet TeamCleaner , en transmettant toutes ses dépendances requises, notamment le
service de base de données. Ensuite, nous créons un objet simple qui imite ce qu'attend la méthode processItem()
et appelle ce dernier tout en lui transmettant le premier. À ce stade, si notre plugin a fait son travail correctement,
l'enregistrement de l'équipe aurait dû être supprimé de la base de données. Nous pouvons donc l'interroger et
cette fois affirmer le contraire de ce que nous faisions auparavant : que la requête revient vide.

Et voilà, notre test est terminé. Comme toujours, nous devrions l'exécuter et nous assurer qu'il réussit :

../vendor/bin/phpunit ../modules/custom/sports/tests/src/
Noyau/[Link]

Et c'est un exemple très simple d'utilisation des tests du noyau pour tester un composant, en particulier
celui qui s'intègre à la base de données. Nous aurions également pu utiliser un test fonctionnel, mais cela aurait
été excessif : il fonctionnerait plus lentement et ne profiterait pas des avantages qu'il offre par rapport aux tests

du noyau, tels que l'intégration du navigateur.

Note
Nous avons opté ici pour une approche presque similaire à celle d'Unit en instanciant
manuellement la classe du plugin TeamCleaner et en lui transmettant des données factices.
Ce qui est intéressant avec les tests du noyau, c'est que nous aurions même pu utiliser le
gestionnaire de plugins de travail et instancier le plugin avec celui­ci.

Test de l'importateur CSV


Après cet exemple simple, écrivons un autre test qui illustre un scénario plus complexe. Et nous en
écrirons un qui teste le plugin CsvImporter que nous avons créé dans le chapitre précédent.
Machine Translated by Google

Tests du noyau 561

De nombreuses fonctionnalités sont intégrées à ce plugin et fonctionnent avec lui : nous avons l'importation proprement dite, la création du plugin et de

l'entité de configuration, l'interface utilisateur pour ce faire, et ainsi de suite. C'est un très bon exemple de fonctionnalités pouvant bénéficier

d'une couverture de test multi­méthodologie. À cet égard, nous commençons par tester son objectif sous­jacent, celui de l'importation du produit,

pour lequel nous n'avons pas besoin d'interactions avec le navigateur.

Cela signifie que nous pouvons utiliser un test du noyau.

De la même manière que nous avons écrit le test précédent, nous pouvons commencer avec la classe comme ceci (cette fois dans le module produits ) :

espace de noms Drupal\Tests\products\Kernel ;

utilisez Drupal\KernelTests\KernelTestBase ;

/**
* Teste l'importateur de produits CSV
*
* produits du @groupe

*/
la classe CsvImporterTest étend KernelTestBase {}

Rien de nouveau jusqu'à présent.

Ensuite, nous devons spécifier les modules que nous devons charger. Et ici, nous avons une plus grande liste :

/**
* {@inheritdoc}
*/
$modules statiques protégés = ['système', 'csv_importer_test', 'produits', 'image', 'fichier',
'utilisateur'];

Seul le module produits peut vous paraître évident à ce stade, mais tout le reste est également
nécessaire. Les modules système, image, fichier et utilisateur sont tous nécessaires d'une
manière ou d'une autre pour gérer le processus de téléchargement et de stockage de fichiers
nécessaire au plugin CsvImporter .
Machine Translated by Google

562 Tests automatisés

Note importante:
Il n'est pas toujours aussi facile de déterminer quels modules sont nécessaires, cela implique
donc un certain processus d'essais et d'erreurs, du moins au début. Un scénario typique
consiste à exécuter le test et à remarquer les échecs dus à des fonctionnalités manquantes.
Suivre cette fonctionnalité dans un module et spécifier ce module dans la liste est la façon
dont vous obtenez généralement une liste complète de modules, en particulier lorsque le test est
complexe et nécessite un large éventail de sous­systèmes avec des dépendances.

Mais vous vous demandez peut­être ce que contient le module csv_importer_test . Souvent, vous devrez peut­être créer
des modules utilisés uniquement pour les tests, généralement parce qu'ils contiennent une configuration que vous
souhaitez utiliser dans vos tests. Dans notre cas, nous l'avons fait pour montrer où iraient ces modules et pour ajouter
un fichier de test [Link] que nous pouvons utiliser dans nos tests.

Les modules de tests se trouvent dans le dossier tests/modules du module qui contient les tests qui les utilisent. Donc,
dans notre cas, nous avons csv_importer_test avec son fichier [Link] :

nom : CSV Importer Description du


test : Utilisé pour tester l'importateur CSV core_version_requirement : ^9

type : module
package : Tests

Et le fichier CSV mentionné que nous utiliserons se trouve juste à côté :

identifiant, nom, numéro_produit


1,Voiture,45345
2, moto, 54534

Maintenant que nous avons couvert cela, nous pouvons écrire la méthode de test :

/**
* Teste l'importation du plugin basé sur CSV.
*/
fonction publique testImport() {
$this­>installEntitySchema('produit');
$this­>installEntitySchema('fichier');
$this­>installSchema('file', 'file_usage');
$entity_type_manager = $this­>container­>get('entity_type.
directeur');
// Affirmer que nous n'avons aucun produit dans le système.
Machine Translated by Google

Tests du noyau 563

$products = $entity_type_manager­>getStorage('product')­ >loadMultiple();

$this­>assertEmpty($produits);

$csv_path = drupal_get_path('module', 'csv_importer_test') .


'/[Link]';
$csv_contents = file_get_contents($csv_path); $file =
file_save_data($csv_contents, 'public://simpletest­[Link]',
FileSystemInterface::EXISTS_REPLACE); $config = $entity_type_manager­
>getStorage('importer')­ >create([

'identifiant' => 'csv',


'label' => 'CSV', 'plugin' =>
'csv', 'plugin_configuration'
=> [ 'file' => [$file­>id()]

],
'source' => 'Test', 'bundle' =>
'goods', 'update_existing' =>
true
]);
$config­>save();

$plugin = $this­>container­>get('products.importer_manager')­ >createInstanceFromConfig('csv');


$plugin­>importer(); $products = $entity_type_manager­
>getStorage('product')­
>loadMultiple(); $this­>assertCount(2, $products);

$products = $entity_type_manager­>getStorage('product')­ >loadByProperties(['number' =>


45345]); $this­>assertNotEmpty($produits); $this­>assertCount(1,
$products);

Et la déclaration d'utilisation en haut :

utilisez Drupal\Core\File\FileSystemInterface ;
Machine Translated by Google

564 Tests automatisés

La configuration initiale ici est un peu plus compliquée, en partie à cause du fait que les tests du noyau
n'installent pas de schémas de module. En utilisant la méthode parent installEntitySchema() , nous pouvons
installer toutes les tables nécessaires pour les entités de contenu Product et File. Cependant, puisque nous
travaillons avec des fichiers gérés, nous devons également installer la table file_usage manuellement. Ce n'est pas
techniquement une table d'entités. Encore une fois, il n’y a aucune honte à arriver à ces étapes par essais et
erreurs.

Maintenant que nous avons configuré les bases, nous pouvons effectuer une vérification de cohérence et nous
assurer que nous n'avons aucune entité de produit dans la base de données. Il n'y a aucune raison pour que
nous en ayons, mais cela ne fait pas de mal de le garantir. Cela garantit un test valide puisque notre objectif sera
d'affirmer ultérieurement l'existence des produits.

Ensuite, nous créons une entité File gérée en utilisant le fichier [Link] du module csv_importer_test .
La fonction drupal_get_path() est un moyen très courant de récupérer le chemin relatif d'un module ou d'un thème, quel
que soit l'endroit où il se trouve réellement. Et nous enregistrons le contenu de ce fichier dans le système de fichiers
public:// de l'environnement de test. Gardez cependant à l’esprit qu’une fois le test exécuté avec succès, ce fichier est
supprimé lorsque Drupal se nettoie après lui­même.

Ensuite, nous devons créer une entité de configuration Importer qui utilise le plugin basé sur CSV pour exécuter
l'importation. Et au lieu de le faire via l’interface utilisateur, nous le faisons par programme.
À l'aide du gestionnaire de stockage, nous créons l'entité comme nous l'avons appris au chapitre 6, Modélisation et
stockage des données. Une fois que nous avons cela, nous utilisons le gestionnaire de plugins Importer pour créer
une instance basée sur cette entité de configuration (à laquelle nous avons donné l'ID csv). Et enfin, nous gérons
l'importation des produits.

Maintenant, pour les assertions, nous effectuons une double vérification. Étant donné que notre CSV de test
contient deux lignes, nous chargeons à nouveau toutes les entités de produit et affirmons que nous en avons
deux au total. Ni plus ni moins. Et ici, nous voyons une autre méthode d'assertion utile pour travailler avec
des tableaux : assertCount(). Mais ensuite, nous devenons un peu plus précis et essayons de charger un
produit qui a une valeur de champ (le nombre) égale à un nombre attendu du fichier CSV de test.
Et affirmer qu’en fait, c’est également le cas.

Nous pourrions même faire quelques affirmations supplémentaires. Par exemple, nous pouvons vérifier
que toutes les valeurs du champ Produit ont été correctement définies. Je vais vous laisser explorer les différentes
manières de procéder, soit en interrogeant sur la base de ces valeurs, soit en affirmant l'égalité entre les valeurs de
champ et celles attendues. Mais il est important de ne pas en faire trop, car cela aurait un impact sur la vitesse
et, dans certains cas, n'ajouterait pas suffisamment de valeur à la couverture du test pour la compenser. Le tout
est de trouver le bon équilibre.

Enfin, avec notre test en place, nous pouvons réellement l'exécuter :

../vendor/bin/phpunit ../modules/custom/products/tests/src/
Noyau/[Link]
Machine Translated by Google

Tests fonctionnels 565

Et ce test devrait également réussir.

Dans la section précédente, nous avons examiné les tests du noyau et avons déclaré qu'il s'agissait essentiellement de
tests d'intégration qui se concentrent sur les composants plutôt que sur les interactions avec le navigateur. Dans
la section suivante, nous passerons au niveau supérieur et parlerons des tests fonctionnels à part entière,
autrement appelés tests de navigateur (en raison du nom de la classe de base que nous devons étendre).

Tests fonctionnels
Les tests fonctionnels dans Drupal utilisent un navigateur simulé (utilisant le populaire émulateur Mink) qui permet
aux utilisateurs de cliquer sur des liens, de naviguer vers des pages, de travailler avec des formulaires et de faire des
assertions concernant les éléments HTML de la page. Ce qu'ils ne permettent pas, c'est de tester les interactions basées
sur JavaScript (voir la section suivante pour celles­ci).

Les tests fonctionnels étendent la classe Drupal\Tests\BrowserTestBase , qui est intégrée à PHPUnit comme
celles que nous avons vues auparavant. La classe de base contient de nombreuses méthodes à la fois pour affirmer
des choses et pour des raccourcis pour effectuer des tâches liées à Drupal (et au Web) : créer des utilisateurs, des
entités, naviguer vers des pages, remplir et soumettre des formulaires, se connecter, etc. Et comme avant, chaque test
(méthode de classe) s'exécute de manière isolée, de sorte que des éléments tels que le contenu et les utilisateurs
ne peuvent pas être partagés entre plusieurs tests mais devraient être recréés (peut­être en utilisant la méthode
setUp() comme nous l'avons déjà vu ) .

Les tests de navigateur effectuent une installation Drupal complète avec un nombre minimal de modules (en
utilisant le profil d'installation Testing). Cela signifie que nous pouvons également spécifier d'installer d'autres
modules, et que le schéma correspondant est également installé. De plus, il est également important de comprendre que
l'installation résultante n'a rien de commun avec notre site de développement actuel. Toute configuration dont nous
avons besoin, nous devons la créer. Il n'y a aucun utilisateur, aucun contenu et aucun fichier. Il s’agit donc d’une
toute nouvelle installation parallèle qui s’exécute pendant la durée d’un seul test et est nettoyée à la fin.

Configuration pour les tests fonctionnels


Avant d'écrire nos tests fonctionnels, nous devons revenir à notre fichier [Link] et modifier certaines
variables d'environnement. Outre la variable SIMPLETEST_DB que nous avons ajustée précédemment, nous
avons également SIMPLETEST_BASE_URL et BROWSERTEST_.
RÉPERTOIRE DE SORTIE. Le premier sert à savoir où l’on peut accéder à l’application dans le navigateur. Ce dernier
est le répertoire dans lequel les données de sortie peuvent être enregistrées par PHPUnit et doit être un chemin local
absolu (par exemple, un dossier dans le dossier des fichiers locaux ) :

/var/www/sites/default/files/browser­output
Machine Translated by Google

566 Tests automatisés

De plus, assurez­vous que l'utilisateur exécutant le test dispose des autorisations nécessaires pour écrire sur les sites/
dossier le plus simple car c'est là que le système de fichiers virtuel est créé pour chaque test. Le moyen le
plus simple de procéder consiste à attribuer la propriété du dossier à l'utilisateur du serveur Web qui exécute
le processus. Dans le cas d'Apache, il s'agit généralement de www­data.

Test de la page Bonjour tout le monde

Le premier test fonctionnel que nous allons écrire concerne la page Hello World que nous avons créée et la
fonctionnalité qui la sous­tend. Nous allons tester si la page affiche le bon Hello World
message, également en fonction de la valeur trouvée dans la configuration. Créons donc la classe correspondante,
naturellement dans le module hello_world , dans le dossier tests/src/Functional :

espace de noms Drupal\Tests\hello_world\Functional ;

utilisez Drupal\Tests\BrowserTestBase ;

/** *
Tests de base de la page principale Hello World.
*
*
@group hello_world */

la classe HelloWorldPageTest étend BrowserTestBase {}

On voit vraiment la cohérence avec les autres types de tests. Mais dans ce cas, comme mentionné, nous
étendons depuis BrowserTestBase.

De plus, comme auparavant, nous pouvons configurer un certain nombre de modules que nous souhaitons installer :

/** *
{@inheritdoc}
*/
$modules statiques protégés = ['hello_world', 'user'];

Nous aurons besoin du module Utilisateur pour le deuxième test que nous exécuterons, qui ira dans la même classe
que celui­ci.

De plus, nous devons également indiquer au test quel thème Drupal il doit utiliser :

/**
* {@inheritdoc}
*/
protégé $defaultTheme = 'stable';
Machine Translated by Google

Tests fonctionnels 567

Nous optons pour Stable, qui contient un balisage de base que nous pouvons affirmer, ou nous aurions également
pu utiliser Stark, ce qui n'est pas le cas. Le choix t'appartient.

Mais passons au premier test, plus simple :

/**
* Teste la page principale Hello World. */

fonction publique testPage() {


$expected = $this­>assertDefaultSalutation(); $config = $this­
>config('hello_world.custom_salutation'); $config­>set('salutation', 'Test de la salutation');

$config­>save();

$this­>drupalGet('/bonjour'); $this­
>assertSession()­>pageTextNotContains($expected); $expected = 'Test de salutation';

$this­>assertSession()­>pageTextContains($expected);
}

Si vous vous en souvenez, notre page /hello affiche un message d'accueil en fonction de l'heure de la journée, sauf si
un administrateur a remplacé ce message via un formulaire de configuration. Nous commençons donc ce test en
affirmant qu'avec une nouvelle installation sans remplacement, nous voyons le message d'accueil basé sur le
temps. Et pour cela, nous créons un message d'assertion séparé car il est un peu verbeux et nous allons le
réutiliser :

/**
* Fonction d'assistance pour affirmer que la salutation par défaut est
présent sur la page.
*

* Renvoie le message afin que nous puissions le réutiliser à plusieurs endroits. */

fonction protégée assertDefaultSalutation() {


$this­>drupalGet('/bonjour');
$this­>assertSession()­>pageTextContains('Notre premier itinéraire'); $heure = nouveau
\DateTime();
$attendu = ''; if ((int)
$time­>format('G') >= 00 && (int) $time­
>format('G') < 12) {
Machine Translated by Google

568 Tests automatisés

$expected = 'Bonjour';
}

if ((int) $time­>format('G') >= 12 && (int) $time­


>format('G') < 18) {
$expected = 'Bonjour';
}

if ((int) $time­>format('G') >= 18) {


$expected = 'Bonsoir';

} $expected .= $this­ ' monde';


>assertSession()­>pageTextContains($expected); retourner $ attendu ;

La toute première chose que nous faisons ici est d'utiliser la méthode drupalGet() pour accéder à un chemin sur le site.
Consultez la signature de la méthode pour toutes les options que vous pouvez lui transmettre. Et la première affirmation
que nous faisons est que la page contient le texte Notre premier itinéraire (qui est le titre de la page). La méthode parent
assertSession() renvoie une instance de WebAssert, qui contient toutes sortes de méthodes pour affirmer la présence
d'éléments sur la page actuelle dans la session Mink. Une de ces méthodes est la méthode générique pageTextContains()
avec laquelle nous vérifions simplement que le texte donné peut être trouvé n'importe où sur la page.

Bien que dans de nombreux cas, affirmer la présence d'une chaîne de texte soit suffisant, vous souhaiterez peut­être
vous assurer qu'il s'agit bien de la bonne chaîne (pour éviter les faux positifs). Par exemple, dans notre cas, nous
pourrions vérifier que c'est bien le titre de la page qui est rendu à l'intérieur d'une balise <h1> .
Nous pouvons procéder ainsi :

$this­>assertSession()­>elementTextContains('css', 'h1', 'Notre première route');

La méthode elementTextContains() peut être utilisée pour rechercher un élément sur la page en fonction d'un
localisateur (sélecteur CSS ou XPath) et affirmer qu'il contient le texte spécifié.
Dans notre exemple, nous utilisons le localisateur de sélecteur CSS et nous essayons de trouver l' élément <h1> .

Si tout cela est correct, nous affirmons que le message de salutation réel est présent sur la page. Malheureusement,
nous devons dupliquer une grande partie du code car cela dépend de l'heure de la journée.
Machine Translated by Google

Tests fonctionnels 569

En revenant à notre méthode de test actuelle, nous pouvons continuer en sachant que le message s'affiche correctement sur

la page. Et la prochaine chose que nous voulons tester est la suivante : s'il existe un objet de configuration hello_world.custom_salutation

avec une salutation

valeur, c'est ce qui doit être montré. Nous le créons donc par programme. Ensuite, nous naviguons à nouveau vers le même

chemin (nous rechargeons essentiellement la page) et vérifions que l'ancien message n'est plus affiché et que le nouveau l'est à la

place.

Donc, si nous effectuons réellement ce test :

../vendor/bin/phpunit ../modules/custom/hello_world/tests/src/
Fonctionnel/[Link]

...Zut. Nous obtenons une erreur :

Behat\Mink\Exception\ResponseTextException : Le texte "Bonsoir tout le monde" apparaît dans le


texte de cette page, mais il devrait
pas.

C'est comme si nous n'avions même pas outrepassé le message de salutation. Mais nous l’avons fait.

Le problème est la mise en cache. Gardez à l’esprit que nous naviguons sur ces pages en tant qu’utilisateurs anonymes et que la mise

en cache est activée sur le site comme dans des scénarios normaux. Au chapitre 11, Mise en cache, j'ai noté ce problème particulier : la

propriété max­age remonte uniquement au niveau de la page pour le cache de page dynamique (utilisateurs connectés) et non pour les

utilisateurs anonymes.

Note importante:
Il s'agit d'un excellent exemple de tests automatisés mettant en lumière les erreurs que nous
introduisons lors du développement et que nous ne remarquons pas. Nous avons très probablement
écrit nos fonctionnalités en désactivant la mise en cache et/ou en visitant toujours la page en tant
qu'utilisateur connecté. C'est une erreur facile à commettre. Heureusement, les tests automatisés
viennent à la rescousse.

La solution à ce problème peut être trouvée en utilisant un kill switch de cache complet. Cela signifie que nous devons modifier un

peu notre logique pour dire à Drupal de ne jamais mettre en cache les pages où notre composant de salutation est affiché. C'est

le prix que nous devons payer pour la nature hautement dynamique de nos fonctionnalités et c'est toujours un bon exercice pour

évaluer si cela en vaut la peine. Il existe bien sûr des solutions beaucoup plus compliquées (et créatives) pour cela, mais nous

opterons pour le simple kill switch.

Le kill switch est en fait facile à utiliser. C'est un service appelé page_cache_kill_switch

que nous devons injecter dans notre service HelloWorldSalutation . Vous devriez maintenant savoir comment procéder, je ne le

répéterai donc pas ici.


Machine Translated by Google

570 Tests automatisés

Ensuite, au début de getSalutation() et getSalutationComponent()


méthodes, il suffit d’ajouter cette ligne :

$this­>killSwitch­>trigger();

Et maintenant, si nous effectuons ce test, nous devrions obtenir un résultat vert.

Test du formulaire Hello World


Le deuxième test fonctionnel que nous allons écrire devrait tester le formulaire de remplacement de salutation lui­même.

Dans la précédente, nous avons interagi directement avec l'API de configuration pour apporter des modifications à la
valeur de configuration. Nous allons maintenant voir si le formulaire permettant de le faire fonctionne réellement. Mais
comme nous pouvons réutiliser beaucoup de choses du test précédent et qu’elles sont très étroitement liées, nous
pouvons l’ajouter à la même classe :

/**
* Teste que le formulaire de configuration pour remplacer le message
travaux.
*/
fonction publique testSalutationOverrideForm() {
$expected = $this­>assertDefaultSalutation(); $this­>drupalGet('/admin/
config/salutation­configuration'); $this­>assertSession()­>statusCodeEquals(403); $account =
$this­>drupalCreateUser(['administrer la configuration du site']); $this­
>drupalLogin($compte); $this­>drupalGet('/admin/config/salutation­configuration');

$this­>assertSession()­>statusCodeEquals(200);
$this­>assertSession()­>pageTextContains('Configuration de salutation'); $this­
>assertSession()­
>elementExists('css', '#edit­
salutation');

$modifier = [
'salutation' => 'Ma salutation personnalisée',

$this­>drupalPostForm(NULL, $edit, 'op'); $this­>assertSession()­


>pageTextContains('Les options de configuration ont été enregistrées');
Machine Translated by Google

Tests fonctionnels 571

$this­>drupalGet('/bonjour'); $this­
>assertSession()­>pageTextNotContains($expected); $this­>assertSession()­
>pageTextContains('Ma salutation personnalisée');

Nous commençons ce test de la même manière, en affirmant que le message dépendant de l'heure est affiché.
Cela prouve également que chaque test s'exécute dans son propre environnement indépendant et que les
modifications apportées à la configuration dans un test n'ont aucun impact sur l'autre. Ils partent tous d’une page vierge.

Ensuite, nous accédons à la page du formulaire de configuration et affirmons que nous n'y avons pas accès. Pour
cela, nous utilisons la méthode d'assertion statusCodeEquals() pour vérifier le code de réponse.
C'est bien car nous devons être connectés avec un utilisateur disposant d'une certaine autorisation.

Note

Les restrictions d'accès sur le formulaire de configuration autorisent tout utilisateur


disposant d'une certaine autorisation. Pour cette raison, notre test doit se concentrer sur
cette autorisation plutôt que sur autre chose qui pourrait indirectement inclure cette
autorisation. Par exemple, il ne faut pas supposer qu’un utilisateur doté du rôle d’administrateur
dispose de cette autorisation.

Nous créons donc un nouveau compte utilisateur en utilisant la méthode pratique drupalCreateUser() , dont
le premier paramètre est un tableau d'autorisations que l'utilisateur doit avoir. Nous pouvons ensuite utiliser
l'entité User résultante avec la méthode drupalLogin() pour nous connecter. Sous le capot, cela accède à la
page de connexion de l'utilisateur, soumet le formulaire, puis affirme que tout s'est bien passé. Nous
pouvons maintenant revenir à la page du formulaire de configuration et devrions y avoir accès, ce que nous
affirmons également. De plus, nous affirmons que nous avons le titre de la page et que nous avons l’élément
HTML du champ de texte de salutation sur la page. Nous le faisons en utilisant la méthode elementExists() , en
utilisant le sélecteur CSS. Encore une fois, consultez WebAssert
pour toutes sortes de méthodes d'assertion qui vous aident à identifier des éléments sur la page.

Il est maintenant temps de soumettre le formulaire et de remplacer le message de salutation. Et nous faisons
cela avec drupalPostForm(), dont le paramètre le plus important est un tableau de valeurs à remplir dans les
éléments du formulaire, saisi par le paramètre name de l'élément HTML du formulaire individuel. Dans notre
cas, nous n’en avons qu’un. Consultez la documentation de cette méthode pour plus d’informations sur tout ce
que vous pouvez faire avec. Une fois le formulaire soumis, la page se rechargera et nous pourrons affirmer la
présence du message de confirmation. Et enfin, nous pouvons revenir au chemin /hello et affirmer que l'ancien
message ne s'affiche plus mais que le nouveau message remplacé le fait à la place.
Machine Translated by Google

572 Tests automatisés

La réexécution de la classe de test devrait désormais inclure également ce nouveau test et tout devrait être vert.
Dans la section suivante, nous intégrerons JavaScript afin de pouvoir également tester les intégrations de navigateurs
plus dynamiques. Mais vous pouvez déjà remarquer que les tests du noyau sont beaucoup plus rapides à exécuter si
vous n'avez pas besoin d'interagir avec un navigateur.

Tests fonctionnels JavaScript


Le dernier type de tests PHP que nous pouvons écrire dans Drupal est le test fonctionnel basé sur JavaScript. Les
tests fonctionnels Javascript sont utiles lorsque nous souhaitons tester des fonctionnalités côté client plus dynamiques
telles que les comportements JavaScript ou les interactions Ajax.

Ils sont une extension des tests fonctionnels classiques, mais qui utilisent WebDriver. Cette dernière est une API
qui permet à des éléments comme Selenium de contrôler des navigateurs tels que Chrome ou Firefox. Drupal utilise
Chrome pour cela, alors assurez­vous que Selenium est installé et que vous travaillez avec le pilote Chrome. Nous
n'aborderons pas cela ici car cela dépend de votre environnement local et des dernières versions actuelles. Mais si
vous suivez le référentiel GitHub accompagnant ce livre, vous devriez être prêt.

En supposant que Selenium soit en cours d’exécution, nous pouvons écrire quelques tests. Mais seulement après
avoir ajouté une autre variable d'environnement au fichier de configuration PHPUnit (assurez­vous que le point de
terminaison Selenium est correct pour vous) :

<env name="MINK_DRIVER_ARGS_WEBDRIVER" value='["chrome", null, "http://


localhost:4444/wd/hub"]'/>

Test de temps
Si vous vous souvenez du chapitre 12, API JavaScript et Ajax, nous avons ajouté à notre composant de salutation Hello
World un petit widget d'heure qui affiche l'heure actuelle en temps réel si la salutation n'est pas remplacée. Ce
composant est alimenté par JavaScript et, plus important encore, ajouté à la page à l'aide de JavaScript.

De plus, dans la section précédente, nous avons rédigé un test fonctionnel pour la page Hello World dans lequel
nous avons affirmé la présence du message de salutation. Cependant, le widget d'heure réelle n'y apparaîtra
jamais car le pilote Mink utilisé dans ces types de tests ne prend pas en charge JavaScript. Donc, si nous voulons
tester cela, nous devons écrire un test FunctionalJavascript .
Machine Translated by Google

Tests fonctionnels JavaScript 573

Comme prévu, ces types de tests suivent les mêmes modèles pour le placement des répertoires et les espaces de
noms. Notre premier cours de test peut donc commencer comme ceci :

espace de noms Drupal\Tests\hello_world\FunctionalJavascript ;

utilisez Drupal\FunctionalJavascriptTests\WebDriverTestBase ;

/**
* Test du simple minuteur Javascript sur la page Hello World.
*
*
@group hello_world */

la classe TimeTest étend WebDriverTestBase {}

À présent, la plupart du code ci­dessus devrait être clair. Cependant, la classe de base que nous étendons cette
fois est la classe WebDriverTestBase , qui est elle­même un enfant de BrowserTestBase.
Fait intéressant, cela n'ajoute pas grand­chose au mélange, à part la configuration du test pour utiliser Selenium
Web Driver et l'ajout de quelques méthodes d'assistance spécifiques à JavaScript. Il s'agit de démontrer que la
plupart des différences entre les tests fonctionnels et fonctionnels Javascript sont déterminées par le pilote Mink réel.

Un ajout extrêmement pratique, cependant, est la possibilité de prendre des captures d'écran. Souvent,
lorsque nous testons les interactions frontend, les choses ne se passent pas comme nous le pensions et nous
ne comprenons pas pourquoi. La méthode parent createScreenshot() nous permet d'enregistrer une capture
d'écran complète d'une page à tout moment que nous pouvons étudier à des fins de débogage. Il suffit de transmettre
le nom du fichier que nous souhaitons enregistrer. Alors vérifiez cela.

Poursuivant notre test, ajoutons les modules que nous souhaitons activer :

/** *
{@inheritdoc}
*/
$modules statiques protégés = ['hello_world'];

Comme prévu, le module Hello World suffit.

Et le thème à utiliser dans l'installation de test :

/**
* {@inheritdoc}
*/
protégé $defaultTheme = 'stable';
Machine Translated by Google

574 Tests automatisés

Maintenant, la méthode de test très simple peut ressembler à ceci :

/**
* Teste la composante temporelle. */

public function testSalutationTime() { $this­>drupalGet('/


hello'); $this­>assertSession()­
>pageTextContains('L'heure est');

$config = $this­>config('hello_world.custom_salutation'); $config­>set('salutation', 'Test de


la salutation');
$config­>save();

$this­>drupalGet('/bonjour'); $this­
>assertSession()­>pageTextNotContains('L'heure est');
}

Nous utilisons exactement les mêmes techniques d'assertion qu'auparavant, mais comme JavaScript est activé, le texte
du widget temporel devrait maintenant apparaître. Et comme auparavant, nous testons également que si la méthode de
salutation est remplacée, le widget horaire n'apparaît pas.

Test de l'importateur CSV


Lors de la découverte des tests du noyau, nous avons écrit un test pour CsvImporter axé sur la fonctionnalité d'importation
étant donné une entité de configuration Importer existante (que nous avons créée par programme). Cependant, un autre aspect
important de cette fonctionnalité est le processus de création de cette entité de configuration car nous nous appuyons sur Ajax
pour injecter dynamiquement les éléments de formulaire liés au plugin Importer sélectionné. Alors écrivons également un test
pour cela.

Comme auparavant, la classe de test peut commencer par quelque chose comme ceci :

espace de noms Drupal\Tests\products\FunctionalJavascript ;

utilisez Drupal\FunctionalJavascriptTests\WebDriverTestBase ;

/**
* Test de création/édition d'entités de configuration Importer
en utilisant l'importateur CSV
*
Machine Translated by Google

Tests fonctionnels JavaScript 575

* produits du @groupe

*/
la classe ImporterFormTest étend WebDriverTestBase {}

Définissons le thème par défaut :

/**
* {@inheritdoc}
*/
protégé $defaultTheme = 'stable';

Et comme toujours, activons certains modules :

/** *
{@inheritdoc}
*/
$modules statiques protégés = ['image', 'file', 'node', 'products', 'csv_importer_test'];

Si nous essayons d'exécuter un test avec ces modules, cela échouera car le module Produits sera activé avant
celui Image. Et, si vous vous en souvenez, nous avons créé un champ Image sur l'entité Product, mais nous
avons oublié de faire du module image une dépendance de celle­ci. Ajoutons donc ceci rapidement au fichier
[Link] :

dépendances :
­ Drupal : image

Note importante:
Le module Node est activé car il définit le contenu d'accès
autorisation, qui est utilisée par l’élément de formulaire principal machine_name . Et cet
élément est utilisé sur le formulaire d'entité importateur, nous en aurons donc besoin pour que
les tests fonctionnent réellement.

Même si nous n’écrivons qu’une seule méthode de test, celle­ci nécessite un certain nombre de préparations que
nous souhaiterions peut­être réutiliser ailleurs. De plus, il semble également plus propre d’être séparé de la
méthode de test réelle. Nous pouvons donc lui ajouter une méthode setUp() à la place :

/** *
{@inheritdoc} */
Machine Translated by Google

576 Tests automatisés

fonction protégée setUp() { parent::setUp();


chmod('public://', 0777);

$csv_path = drupal_get_path('module', 'csv_importer_test') . '/[Link]'; $csv_contents =


file_get_contents($csv_path);
$this­>file = file_save_data($csv_contents, 'public://

simpletest­[Link]', FileSystemInterface :: EXISTS_


REMPLACER);

$this­>admin = $this­>drupalCreateUser(['administrer la configuration du site']);

ProductType::create(['id' => 'goods', 'label' => 'Goods'])­


>enregistrer();

Et les nouvelles déclarations d'utilisation :

utilisez Drupal\products\Entity\ProductType ; utilisez


Drupal\Core\File\FileSystemInterface ;

Comme prévu, la première chose que nous faisons est la même chose que lors du test précédent : charger le fichier CSV de
test à partir du module csv_importer_test et "télécharger" sur Drupal en créant une nouvelle entité de fichier gérée. Mais
avant cela, nous définissons les autorisations pour permettre aux fichiers d'être téléchargés dans le dossier public du site de
test. Selon votre configuration de test, cela peut ne pas être nécessaire.

Ensuite, nous créons un compte utilisateur administrateur disposant de l'autorisation nécessaire pour créer des entités de
configuration Importateur, ainsi qu'un ensemble pour l'entité Produit afin que nous puissions réellement créer des produits. Nous
n'avons pas eu à nous soucier du bundle lors du test précédent car nous avons créé la configuration de l'importateur par
programme. Mais maintenant, via l’interface utilisateur, un bundle doit exister pour pouvoir le sélectionner.

L'entité de fichier résultante et le compte utilisateur administrateur que nous stockons dans les propriétés de classe, nous
devons donc également les définir :

/** *
@var \Drupal\file\FileInterface */

fichier $ protégé ;

/** *
@var \Drupal\Core\Session\AccountInterface
Machine Translated by Google

Tests fonctionnels JavaScript 577

*/
protégé $admin ;

Et avec cela, nous sommes prêts à écrire notre méthode de test vide et à commencer à la remplir étape par étape :

/**
* Teste le formulaire d'importateur. */

fonction publique testImporterForm() {}

Nous pouvons commencer par les bases :

$this­>drupalGet('/admin/structure/importer/add'); $assert = $this­>assertSession();

$assert­>pageTextContains('Accès refusé');

Nous accédons au formulaire de création d'entités de configuration d'importateur et affirmons que l'utilisateur n'y a
pas accès. En effet, par défaut, nous naviguons en tant qu'utilisateurs anonymes.
Ensuite, nous devons nous connecter et réessayer ceci :

$this­>drupalLogin($this­>admin); $this­>drupalGet('/
admin/structure/importer/add'); $assert­>pageTextContains('Ajouter un importateur');
$assert­>elementExists('css', '#edit­label');

$assert­>elementExists('css', '#edit­plugin'); $assert­>elementExists('css',


'#edit­update­existing');
$assert­>elementExists('css', '#edit­source');
$assert­>elementExists('css', '#edit­bundle'); $assert­>elementNotExists('css',

'input[name="files[plugin_
configuration_csv_file]"]');

Nous utilisons la même méthode drupalLogin() et revenons au formulaire. Cette fois, nous affirmons que nous
avons le titre ainsi que divers éléments HTML, les éléments de formulaire utilisés pour créer l'entité. De plus, nous

affirmons également que nous n'avons pas l'élément permettant de télécharger le fichier CSV car celui­ci ne devrait
apparaître que si nous sélectionnons que nous souhaitons utiliser le plugin CSV Importer.
Machine Translated by Google

578 Tests automatisés

Il s'ensuit que nous faisons exactement cela :

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


>selectFieldOption('plugin', 'csv'); $this­>assertSession()­
>assertWaitOnAjaxRequest();
$assert­>elementExists('css', 'input[name="files[plugin_
configuration_csv_file]"]');

En utilisant la méthode getSession() , nous obtenons la session Mink actuelle, à partir de laquelle nous pouvons
obtenir l'objet représentant la page réelle que nous regardons. Ceci est un DocumentElement
objet qui peut être traversé, inspecté et manipulé de toutes sortes de manières. Je vous recommande de consulter la
classe TraversableElement pour toutes les méthodes disponibles.

L'une de ces méthodes est selectFieldOption() par laquelle nous pouvons spécifier le localisateur d'un élément de
sélection HTML (ID, nom ou étiquette) et une valeur, et elle déclenchera la sélection. Comme vous le savez, ceci est
censé faire une requête Ajax apportant nos nouveaux éléments de formulaire.
Et en utilisant assertWaitOnAjaxRequest() sur l' objet JSWebAssert , nous pouvons attendre que cela soit terminé. Enfin,
nous pouvons affirmer que le champ de téléchargement de fichier est présent sur la page.

Ensuite, nous procédons au remplissage du formulaire :

$page­>fillField('label', 'Test de l'importateur CSV'); $this­


>assertJsCondition('jQuery(".machine­name­value").html() == "test_csv_importer"'); $page­
>checkField('update_existing'); $page­
>fillField('source', 'test'); $page­>fillField('bundle', 'goods');
$wrapper = $this­>container­>get('stream_wrapper_manager')­

>getViaUri($this­>file­>getFileUri()); $page­
>attachFileToField('files[plugin_configuration_csv_
fichier]', $wrapper­>realpath()); $this­
>assertSession()­>assertWaitOnAjaxRequest();
$page­>pressButton('Enregistrer');
$assert­>pageTextContains('Création de l'importateur de test CSV.');

La méthode générique fillField() est utile pour des éléments tels que les champs de texte, tandis que la méthode
checkField() est censée être utile pour les cases à cocher. Le localisateur pour les deux est à nouveau soit l'ID, le
nom ou l'étiquette de l'élément.
Machine Translated by Google

Tests fonctionnels JavaScript 579

Nous utilisons également la méthode assertJsCondition pour que l'exécution attende qu'une
modification JavaScript se produise sur la page. Et nous faisons cela pour nous assurer que le
champ du nom de la machine de l'entité est actuellement rempli.

Ensuite, à l'aide du wrapper de flux du fichier que nous avons téléchargé, et plus précisément
de sa méthode realpath() , nous attachons le fichier au champ à l'aide de la méthode
attachFileToField() . Cela déclenche une requête Ajax, que nous attendons encore une fois. Enfin,
nous utilisons la méthode pressButton() pour cliquer sur le bouton Soumettre puis affirmer que
nous avons imprimé un message de confirmation (le formulaire a été enregistré et la page actualisée).

Maintenant, pour vérifier que l'opération s'est bien déroulée :

$config = Importateur::load('test_csv_importer'); $this­


>assertInstanceOf(Drupal\products\Entity\
ImporterInterface::class, $config);

$fids = $config­>getPluginConfiguration()['fichier'];
$fid = réinitialiser ($fids);
$file = Fichier::load($fid); $this­
>assertInstanceOf(Drupal\file\FileInterface::class, $file);

Et les nouvelles déclarations d'utilisation :

utilisez Drupal\file\Entity\File ; utilisez


Drupal\products\Entity\Importer ;

Nous chargeons l'entité de configuration en utilisant l'ID que nous lui avons donné, puis affirmons que
l'objet résultant est une instance de l'interface correcte. Cela vérifie si nous avons réellement enregistré
l'entité. Ensuite, nous chargeons l'entité File en fonction de l'ID trouvé dans l'entité de configuration
Importer et affirmons qu'elle implémente également l'interface correcte. Cela prouve que le fichier a
bien été enregistré et que la configuration est correcte.

Au lieu de vérifier le reste des valeurs des champs par programme, de la même manière, nous optons
pour accéder au formulaire d'édition de l'entité Importateur et affirmer que les valeurs sont pré­
remplies correctement :

$this­>drupalGet('admin/structure/importer/test_csv_importer/
modifier');
$assert­>pageTextContains('Modifier l'importateur CSV de test'); $assert­
>fieldValueEquals('label', 'Test de l'importateur CSV'); $assert­>fieldValueEquals('plugin', 'csv');
Machine Translated by Google

580 tests automatisés

$assert­>checkboxChecked('update_existing'); $assert­
>fieldValueEquals('source', 'test'); $page­>hasLink('[Link]');

$assert­>fieldValueEquals('bundle', 'Biens (marchandises)');

Les méthodes fieldValueEquals() et checkboxChecked() sont pratiques pour vérifier les valeurs des
champs. De plus, nous utilisons également la méthode hasLink() pour vérifier s'il existe un lien portant ce nom
sur la page. Il s'agit en fait de prouver que le fichier téléchargé s'affiche correctement :

Figure 17.1 : Configuration du plugin pour l'importateur CSV

Et enfin, puisque le champ bundle est un champ de référence et non un simple champ de texte, nous devons
construire la valeur que le framework de test y voit réellement, qui se trouve dans ce modèle : Label (ID) .

Et voilà, notre test est terminé et nous pouvons l’exécuter dans son intégralité :

../vendor/bin/phpunit ../modules/custom/products/tests/src/
Noyau/[Link]

Résumé
Dans ce chapitre, nous avons parlé un peu des tests automatisés dans Drupal 9. Nous avons commencé par
une introduction expliquant pourquoi il est utile et réellement important d'écrire des tests automatisés, puis
nous avons brièvement abordé quelques­uns des types les plus populaires de méthodologies de test de
développement logiciel.

Drupal a la capacité d'utiliser un grand nombre de méthodologies, comme nous l'avons vu. Nous avons des
tests unitaires, la forme de test de niveau le plus bas qui se concentre sur des unités architecturales uniques et
qui sont de loin les tests les plus rapides de tous. Ensuite, nous avons les tests du noyau, qui sont des
tests d'intégration axés sur les composants de niveau inférieur et leurs interactions. Ensuite, nous avons les tests
fonctionnels, qui sont des tests de niveau supérieur axés sur les interactions avec le navigateur.
Et enfin, nous avons les tests FunctionalJavascript, qui étendent ce dernier et intègrent Selenium et
Chrome pour permettre de tester des fonctionnalités qui dépendent de JavaScript.
Machine Translated by Google

Résumé 581

Nous avons également vu que tous ces différents types de tests sont intégrés à PHPUnit afin que nous puissions
tous les exécuter à l'aide de cet outil. Cela signifie que tous les différents types de tests suivent les mêmes
« règles » pour leur enregistrement auprès de Drupal, à savoir l'emplacement du répertoire, l'espacement des
noms et les informations PHPDoc.

Le monde des tests automatisés est immense et il ne peut y avoir un seul chapitre dans un livre qui puisse couvrir
toutes les différentes façons dont quelque chose peut être testé. Pour cette raison, en particulier pour les
débutants, le cheminement vers une bonne couverture des tests est semé d’essais et d’erreurs et comporte même
parfois des frustrations. Mais nous obtenons ainsi un code stable qui fonctionne toujours et qui est protégé des
régressions.

Dans le prochain et dernier chapitre, nous examinerons quelques mesures que nous pouvons prendre pour protéger
nos applications Drupal contre les attaques malveillantes.
Machine Translated by Google

Vous aimerez peut-être aussi