Objectifs de la séance ¶
- Installer
Composer - Créer un nouveau projet avec
Composer - Installer
PHPUnit - Découvrir
PhpStorm - Installer des outils de tests statiques dans
PhpStorm - Installer et configurer
PHP Mess Detector(a.k.a.PHPMD) - Installer et configurer
PHP Coding Standards Fixer(a.k.a.PHP CS Fixer) - Écrire des tests unitaires
- Écrire des tests d'intégration
- Prendre en main
PHPUnit - Utiliser des Stubs
- Utiliser des Mock objects
- Découvrir l'analyse de couverture de code
Mise en place du projet ¶
Quelques considérations liées à la configuration système de l'IUT ¶
Si vous travaillez sur votre ordinateur personnel, sur Linux ou sur Windows, vous pouvez placer le répertoire « tests-conception » du projet où bon vous semble. La partie qui suit concerne uniquement le travail sur les ordinateurs de l'IUT. Cela ne vous empêche pas d'en prendre connaissance.
Votre répertoire d'accueil « $HOME » se trouve sur un emplacement réseau NFS, ce qui vous permet de retrouver vos fichiers quel que soit le PC du département sur lequel vous vous connectez. Symfony utilise le sous-répertoire « var » du projet pour la gestion de son cache, des traces d'exécution et des données de session. Les traces d'exécution et plus particulièrement le cache sont des tâches générant de nombreuses entrées-sorties sur le système de fichiers, particulièrement inadaptées aux systèmes de fichiers en réseau. Il en va de même pour PhpStorm qui surveille les modifications de tous les fichiers de votre projet et ne peut pas fonctionner normalement lorsque ces fichiers sont sur un partage NFS. Afin d'améliorer les performances de votre application et d'éviter de saturer le serveur de fichiers du département informatique, vous allez travailler dans le répertoire « /working/votre_login » de la machine sur laquelle vous êtes connecté.
En fin de séance ¶
En fin de séance, vous devez impérativement synchroniser votre dépôt distant (en ligne de commande ou avec PhpStorm)
git commitpuis
git pushfaute de quoi vous perdrez votre travail entre chaque séance !
Pensez également à supprimer votre répertoire de travail /working/votre_login/tests-conception pour éviter la saturation du disque local :
rm -Rf /working/votre_login/tests-conception
En début de séance ¶
Lorsque vous reprenez le travail la séance suivante, vous avez trois choix possibles, qui dépendent de la façon dont vous avez travaillé :
- Mettre à jour votre dépôt local
cd /working/votre_login/tests-conception
git pull
- Effacer votre dépôt local et repartir de zéro
cd /working/votre_login
rm -Rf /working/votre_login/tests-conception
git clone https://iut-info.univ-reims.fr/gitlab/votre_login/tests-conception.git
cd /working/votre_login/tests-conception
- Réinitialiser votre dépôt local selon le dépôt distant
cd /working/votre_login/tests-conception
git fetch
git reset --hard origin/main
Ensuite, dans le répertoire de votre projet, vous devez et (ré)installer les composants nécessaires à son fonctionnement :
composer install
Versionnage du projet ¶
Pour chaque nouveau sujet de TP, vous créerez un nouveau répertoire, vous initialiserez un dépôt Git dans ce répertoire et créerez un dépôt distant sur GitLab. Vous réaliserez une opération de validation avec git commit au moins une fois par question du sujet.
Initialisation du dépôt ¶
- Ouvrez un terminal, utilisez la commande
mkdirpour créer un nouveau répertoiretests-conceptiondans/working/votre_loginet utilisez la commandecdpour vous placer dans le répertoire - Initialisez le dépôt Git du projet
git init
- Éditez le fichier
.gitignorepour exclure les fichiers binaires de type image (JPEG, PNG, …), les fichiers qui contiennent des mots de passe ou les fichiers créés par votre éditeur de texte (*.swppourvim, par exemple) - Éditez un fichier
README.mddans lequel doivent figurer votre nom et votre prénom - Ajoutez les fichiers au dépôt
git add .
- Effectuez une première validation
git commit -m "Initial commit"
- Connectez-vous à l'application GitLab et créez un dépôt portant le même nom que votre nouveau répertoire
- Accordez la permission «
Reporter» à votre enseignant de TP - Associez le dépôt local et le dépôt distant
git remote add origin https://iut-info.univ-reims.fr/gitlab/votre_login/tests-conception.git
- Poussez le dépôt local vers le dépôt distant
git push
Consignes ¶
- Vous effectuerez une validation après chaque question.
- A la fin de chaque séance, vous effectuerez une validation. Si cette validation contient du code incomplet ou ne fonctionnant pas, mentionnez-le dans le message de validation. Vous pousserez ensuite votre travail vers le dépôt distant.
- Ce dépôt sera utilisé par votre enseignant(e) de TP pour évaluer votre travail. Assurez-vous donc régulièrement que tous les fichiers que vous souhaitez lui rendre sont bien présents dans le dépôt. Respectez les noms des fichiers qui vous sont demandés si des consignes particulières vous sont données.
- Le dépôt lui-même sera évalué : soignez l'écriture de vos messages.
L'aide-mémoire Git et l'aide-mémoire GitLab de Monsieur Nourrit pourront vous être utiles.
Nouveau projet à partir de celui du précédent sujet de TP ¶
Afin de gagner du temps, vous allez réutiliser le projet de votre précédent sujet de TP. Vous allez donc cloner votre dépôt distant dans le répertoire de travail local, puis supprimer tous les fichiers sources et de tests pour repartir d'une base vierge, mais déjà configurée pour l'utilisation des outils de qualité de code et de tests unitaires.
- Placez-vous dans votre répertoire de travail dans
working:cd /working/$USER
- Clonez votre dépôt distant dans le répertoire
tests-conception:git clone https://iut-info.univ-reims.fr/gitlab/$USER/test-phpunit.git tests-conception
- Placez-vous dans le répertoire du projet cloné :
cd tests-conception
- Supprimez le dépôt
Gitdu projet cloné pour repartir d'une base vierge :rm -Rf .git
- Supprimez tous les fichiers sources et de tests :
rm -Rf {src,tests}/* - Lancez l'installation des dépendances du projet :
composer install
- Vérifiez le contenu du fichier «
.gitignore» pour vous assurer que les fichiers et répertoires à ne pas suivre sont correctement excluscat .gitignore
dont le résultat doit être similaire à :/vendor/ /.idea/ /.phpunit.cache /.php-cs-fixer.cache
- Initialisez un nouveau dépôt
Git:git init
- Vérifiez le contenu de l'index
Gitavec la commande :git status
dont le résultat doit être :Sur la branche main Aucun commit Fichiers non suivis: (utilisez "git add <fichier>..." pour inclure dans ce qui sera validé) .gitignore .php-cs-fixer.dist.php composer.json composer.lock grumphp.yml pdepend.xml phpunit.xml ruleset.xml
- Lancez
PhpStormpour ouvrir le nouveau projet :phpstorm . &
- Modifiez le fichier «
composer.json» pour corriger le nom du projet et sa description - Ajoutez tous les fichiers à l'index
Git:git add .
- Effectuez un premier «
commit» pour enregistrer l'état initial du projet et vérifiez queGrumPHPse déclenche biengit commit -m "Initial commit"
- Créez un nouveau projet «
tests-conception» sur https://iut-info.univ-reims.fr/gitlab/ et ajoutez le dépôt distant à votre projet local :git remote add origin https://iut-info.univ-reims.fr/gitlab/$USER/tests-conception.git
- Poussez les modifications sur le dépôt distant :
git push -u origin main
Principe du jeu et de la réalisation ¶
Vous allez vous intéresser au développement d'un jeu « Rock, Paper, Scissors » et ses évolutions possibles à travers l'ajout de nouveaux gestes comme dans la variante « Rock, Paper, Scissors, Lizard, Spock ». Le jeu sera exécuté en PHP ligne de commande.
Le principe de ces jeux repose sur l'opposition de deux joueurs qui choisissent chacun un geste parmi 2n+1. Chaque geste l'emporte contre n gestes et perd contre n autres gestes. Si les deux joueurs choisissent le même geste, il y a égalité.
Les combinaisons gagnantes pour « Rock, Paper, Scissors » (n=1) sont triviales :
Les combinaisons gagnantes pour « Rock, Paper, Scissors, Lizard, Spock » (
n=2) sont :
Vous pouvez consulter des variantes à 7, 9, 11, 15, 25 voire 101 gestes.
La réalisation de ces jeux va se faire, en fonction des spécifications, selon les bonnes pratiques de conception afin de permettre flexibilité, robustesse et évolutivité, avec une implémentation réfléchie et des tests garantissant le bon fonctionnement des composants et de l'ensemble.
Rock, Paper, Scissors ¶
Pour commencer, la réalisation va se limiter à la variante la plus simple du jeu avec trois gestes.
Gestion des règles ¶
Une partie du fonctionnement du jeu réside dans la détermination du geste gagnant. Lorsque les gestes des deux opposants sont confrontés, si le premier est gagnant, il remporte 1 point, s'il est perdant, il perd 1 point et en cas d'égalité, le résultat est nul.
- Observez le diagramme de classes
Le diagramme précédent est celui des classes associées aux règles.
- Créez l'interface et les classes avec l'assistant de
PhpStorm - Écrivez les méthodes de la classe «
RockPaperScissors»- «
getGestures()» donne l'ensemble des gestes - «
compare()» retourne1,-1ou0selon que «gesture1» gagne, perd ou est à égalité avec «gesture2» - «
checkGesture()» vérifie que son paramètre est un geste valide - «
gestureIndex()» donne l'indice du geste dans le tableau «GESTURES» afin de faciliter l'évaluation de la comparaison (cette méthode n'est pas obligatoire, vous pouvez réaliser «compare()» sans elle)
- «
- Écrivez les tests de la classe «
RockPaperScissors»Remarque importanteLes classes de test font partie de l'espace de noms «
Tests» suivi de l'espace de noms de la classe qu'elles testent.
Les joueurs ¶
Les joueurs, machine ou humain, peuvent jouer un coup valide.
- Observez le diagramme de classes
- Créez les classes et interface avec
PhpStorm - Écrivez les méthodes de la classe «
Player» - Écrivez la méthode de la classe «
Computer» qui réalise un tirage aléatoire parmi l'ensemble des gestes connus dans les règles - Écrivez les méthodes de la classe «
Human»- «
showPossibilities()» retourne l'ensemble des gestes possibles sous forme d'une chaîne de caractères - «
prompt()» affiche les gestes possibles et invite l'utilisateur à en choisir un - «
getInput()» effectue une lecture au clavier et la retourne - «
playOneMove()» qui effectue toutes les opérations nécessaires pour récupérer un geste valide du joueur
- «
- Écrivez les tests des classes «
Human» et «Computer»Remarque importanteLes classes de test font partie de l'espace de noms «
Tests» suivi de l'espace de noms de la classe qu'elles testent.NamespacesLa classe «
Human» produisant une sortie écran, lisez la documentation concernant ce point.
Le jeu ¶
Le jeu nécessite deux joueurs et un jeu de règles.
- Observez le diagramme de classes
- Créez la classe avec
PhpStorm - Écrivez les méthodes de la classe «
Game»- le constructeur
- «
play()» qui lance la partie pour un certain nombre de tours minimum jusqu'à obtenir un vainqueur et qui cumule dans «score» les résultats obtenus par «playOneMove()» - «
playOneMove()» qui correspond à un tour de jeu - «
getScore()» - «
winner()» qui retourne le vainqueur ou lève une «RuntimeException» s'il n'y en a pas
- Écrivez les tests de la classe «
Game» en utilisant les exécutions successives simulées («willReturnOnConsecutiveCalls()») de la méthode «playOneMove()» pour simuler une partie complèteNamespacesLes classes de test font partie de l'espace de noms «
Tests» suivi de l'espace de noms de la classe qu'elles testent.Sortie écranVotre programme produit une sortie écran qui va polluer l'affichage du résultat de
PHPUnit, mais également lui faire considérer les tests comme risqués. Une solution consiste à mettre des attentes (expectations en anglais) de sortie écran dans vos tests, avec une contrainte minimale sur ces attentes. Ainsi, l'attente suivante devrait être vérifiée par tous les tests dont la sortie écran est non vide et que nous ne souhaitons pas tester précisément :$this->expectOutputRegex('/./');
Couverture de code ¶
Tous les composants nécessaires étant présents et testés, il est utile de vérifier la couverture de code.
- Générez un rapport d'analyse de couverture de code dans le répertoire «
coverage» - Excluez ce répertoire
coveragede votre dépôtGit - Ajoutez un nouveau script
Composer«test:coverage» qui efface le répertoire «coverage» avant de lancer la commandePHPUnitque vous venez d'élaborer - Vérifiez que votre couverture de code est suffisante
Programme pour jouer ¶
Tous les composants nécessaires étant présents, il reste à pouvoir jouer.
- Créez un répertoire «
bin» dans lequel vous mettrez vos programmes - Dans le répertoire «
bin», créez un programme «playRPS» qui commence par :#!/usr/bin/env php <?php
- Rendez le programme «
playRPS» exécutableInformationVous pouvez à présent lancer vos programmes avec :
bin/nom_programme
- Ajoutez au début du programme l'inclusion de l'auto-chargement de
Composeren utilisant la constante magique «__DIR__» pour déterminer le chemin du programme - Écrivez le programme «
playRPS» qui permet à un humain « Bob » de jouer contre la machine « HAL » pour 3 tours minimum, qui pourrait donner :bin/playRPS Saisissez votre geste (R, P, S) : R R R --> 0 Saisissez votre geste (R, P, S) : o Saisissez votre geste (R, P, S) : R R S --> 1 Saisissez votre geste (R, P, S) : R R P --> -1 Saisissez votre geste (R, P, S) : P P R --> 1 Bob won!
Rock, Paper, Scissors, Lizard, Spock ¶
Vous allez reproduire ce qui vient d'être fait pour « Rock, Paper, Scissors » afin d'arriver à la création des composants nécessaires à la variante « Rock, Paper, Scissors, Lizard, Spock ».
- Dupliquez la classe «
RockPaperScissors» en «RockPaperScissorsLizardSpock» - Complétez les gestes possibles dans les constantes de classe : «
'R'», «'P'», «'S'», «'L'», «'V'» («'V'» pour « Vulcan Salute » 🖖) - Vous pouvez construire un tableau des gestes gagnants sur le modèle « Rock gagne face à Scissors et Lizard », …
- Donnez une version fonctionnelle de la méthode «
compare()» - Écrivez les tests permettant de le vérifier
- Écrivez le programme «
playRPSLV» qui permet à un humain « Bob » de jouer contre la machine « HAL » pour 3 tours minimum
Généralisation et refonte ¶
En fonction de la façon dont vous avez réalisé la variante « Rock, Paper, Scissors, Lizard, Spock », votre code est plus ou moins redondant par rapport à « Rock, Paper, Scissors ». Qu'en sera-t-il pour les variantes comportant plus de gestes ?
En réorganisant astucieusement les gestes en « Rock, Spock, Paper, Lizard, Scissors », les scores associés aux combinaisons sont :
| R | V | P | L | S | |
|---|---|---|---|---|---|
| R | 0 | -1 | -1 | 1 | 1 |
| V | 1 | 0 | -1 | -1 | 1 |
| P | 1 | 1 | 0 | -1 | -1 |
| L | -1 | 1 | 1 | 0 | -1 |
| S | -1 | -1 | 1 | 1 | 0 |
Dans une vision cyclique des gestes, pour un geste donné, les deux suivants l'emportent alors que les deux précédents sont battus :
La généralisation à 2n+1 gestes donne les n gestes suivants vainqueurs et les n gestes précédents perdants. Le fonctionnement réside alors sur l'ordre des gestes qui permet de connaître les points pour chaque confrontation, ce qui va entrainer la création d'une nouvelle hiérarchie de classes.
Les gestes ¶
Vous allez séparer les gestes des règles :
- Créez une interface «
MyGame\Rules\Gestures\IGestures» représentant les gestes associés à des règles - Ajoutez une méthode «
getGestures()» à votre nouvelle interface - Parcourez rapidement la documentation de «
ReflectionClass» et lisez attentivement celle de sa méthode «getConstants()» - Créez une classe abstraite «
MyGame\Rules\Gestures\Gestures» qui implémente «IGestures» et qui aura pour but de retourner les constantes des classes qui dériveront d'elle - Lisez la documentation concernant la résolution statique à la volée (« Late Static Bindings »)
- Donnez le code suivant à la méthode «
getGestures()» :$reflectionClass = new ReflectionClass(static::class); return array_values($reflectionClass->getConstants());
- Testez votre classe abstraite «
Gestures» en créant une classe anonyme en héritant - Dérivez la classe abstraite «
Gestures» en «RockPaperScissorsLizardSpockGestures» - Ajoutez les constantes des gestes à votre nouvelle classe
- Testez «
RockPaperScissorsLizardSpockGestures»
Les règles ¶
Les règles vont également être revues :
- Créez avec
PhpStormla classe «GenericRules» qui implémente «IRules» - Ajoutez à «
GenericRules» un constructeur qui reçoit une instance de «IGestures» qui sera mémorisée dans une propriété d'instance - En vous appuyant sur les principes énoncées au début de cette partie, donnez une version fonctionnelle des méthodes :
- «
getGestures()» - «
checkGesture()» - «
compare()» - «
getGestureIndex()»
- «
- Modifiez la classe «
RockPaperScissorsLizardSpock» afin qu'elle hérite de «GenericRules» et qu'elle ne comporte plus qu'un constructeur - Adaptez éventuellement les tests de «
RockPaperScissorsLizardSpock» et vérifiez qu'ils passent - Modifiez la classe «
RockPaperScissors» afin qu'elle hérite de «GenericRules» et qu'elle ne comporte plus qu'un constructeur - Adaptez éventuellement les tests de «
RockPaperScissors» et vérifiez que votre classe les passe
Couverture de code ¶
Tous les composants nécessaires étant présents et testés, il est utile de vérifier la couverture de code.
- Générez un rapport d'analyse de couverture de code dans le répertoire «
coverage» - Vérifiez que votre couverture de code est suffisante
Vous devrez fixer le mode de fonctionnement de Xdebug en préfixant votre commande de calcul de couverture de code par
l'affectation de la variable d'environnement XDEBUG_MODE avec la valeur « coverage » :
XDEBUG_MODE=coverage vendor/bin/phpunit
RPS-9 ¶
Mettons votre code à l'épreuve de RPS-9.
- Téléchargez le code de la classe de gestes
RockGunWaterAirPaperSpongeHumanScissorsFireGestures.php(télécharger) - Téléchargez le code de la classe de règles
RockGunWaterAirPaperSpongeHumanScissorsFire.php(télécharger) - Téléchargez le code de la classe de tests
RockGunWaterAirPaperSpongeHumanScissorsFireTest.php(télécharger) - Vérifiez que vos classes passent ces nouveaux tests