Aller au contenu

TP 3

Finalisation du contrôleur, tests d’intégration et exigences avancées de qualité du code.

Meta

  • Date limite : Dimanche 12 avril, 23 h 59
  • Remise : GitLab
  • Équipes : Triplets d’étudiants, tel qu’annoncé en classe
  • Remise en retard : Impossible. Vous serez évalués selon le contenu présent sur votre branche principale à la date limite

Il est de votre responsabilité de soumettre votre code en temps opportun. N’attendez pas à la dernière minute, GitLab peut échouer.

Objectifs d’apprentissage

Dans ce troisième TP, vous apprendrez à appliquer des exigences avancées de qualité du code sur du code existant et nouveau. De plus, vous apprendrez à utiliser des joueurs robotiques comme tests d’intégration, afin de guider une implémentation correcte des règles du jeu.

Plus concrètement, pour le troisième TP, vous devrez...

  1. compléter votre implémentation du Controller de Skyjo, et intégrer des concepts avancés pour assurer la qualité du code dans le processus de build. Toutes les exigences de qualité du code doivent être appliquées via pom.xml.

  2. Votre code doit être testé avec des tests classiques et des tests de mutation. Vous devez vous assurer qu’un niveau de couverture et de couverture de mutation requis est atteint.

  3. Votre code doit respecter des règles de formatage de code de niveau industriel, et respecter un seuil de complexité requis.
  4. Votre code doit être entièrement documenté avec des commentaires JavaDoc pour toutes les méthodes et classes publiques.

  5. utiliser les tests d’intégration fournis pour guider la complétion de votre implémentation du contrôleur, c’est-à-dire l’implémentation des règles du jeu.

  6. Votre implémentation du jeu doit prendre en charge correctement toutes les actions du jeu, dans toutes les situations.

  7. Les décideurs fournis (joueurs robotiques déterministes) serviront de référence pour vérifier si votre implémentation du modèle et du contrôleur respecte correctement les règles. Ces joueurs robotiques sont utilisés dans les tests d’intégration fournis. Si un test d’intégration échoue, vous devez comparer les parcours d’exécution attendus et réels du jeu afin d’identifier et corriger les erreurs potentielles d’implémentation.

Aperçu étape par étape

  1. Corrections : Vérifiez votre soumission précédente.
    • Utilisez les rétroactions reçues sur GitLab (cela peut encore prendre quelques jours, vous pouvez déjà relancer vos tests).
    • Corrigez tout problème, en particulier les tests échoués. Utilisez le débogueur.
  2. Qualité du code :
    • Activez un seuil de complexité cyclomatique de 7, via google_checks.xml et pom.xml.
    • Activez une vérification stricte du Javadoc via pom.xml.
    • Augmentez la couverture de code à 85% via pom.xml.
    • Activez les tests de mutation, et définissez une couverture minimale des tests de mutation via pom.xml.
  3. Code du contrôleur : Complétez votre implémentation du contrôleur afin que le jeu soit entièrement jouable.
  4. Tests d’intégration :
    • Séparez les tests d’intégration des tests réguliers en utilisant le suffixe *IT via pom.xml.
    • Étendez la classe abstraite de test d’intégration fournie pour activer les tests d’intégration, et assurez-vous que tous les tests d’intégration passent.

Vous pouvez entrelacer 3. et 4.

Les tests d’intégration sont également un moyen efficace de guider l’implémentation (voir TDD), c’est-à-dire que vous pouvez d’abord activer les tests d’intégration, puis finaliser progressivement votre base de code jusqu’à ce que tous les tests d’intégration s’exécutent correctement. Les tests d’intégration fournis sont aussi progressifs, c’est-à-dire que vous pouvez commencer par un test court qui ne joue que quelques tours, puis passer aux versions plus complexes par la suite.

Instructions détaillées

Plusieurs changements sont nécessaires pour réussir ce troisième jalon. Voici une description détaillée de ce qui est attendu.

Corrections

  • Vérifiez que vous avez bien étendu tous les tests requis du jalon TP2.
  • Vérifiez que votre implémentation passe tous les tests précédents.
  • Dès que vous recevez une rétroaction individuelle sur votre soumission TP2, assurez-vous de corriger tous les problèmes avant d’ajouter de nouvelles fonctionnalités.

Qualité du code

Pour ce TP3, votre code doit respecter diverses métriques de qualité.

  • Il ne s’agit pas d’options. Il ne suffit pas de les vérifier manuellement dans votre IDE.
  • Elles doivent être appliquées sous forme de plugins Maven.
  • Les plugins ne doivent pas être configurés pour émettre uniquement des avertissements, c’est-à-dire que la compilation doit échouer s’il y a des problèmes dans votre code.

Votre soumission sera rejetée si les mesures de qualité du code ont été ignorées. Ce n’est pas un luxe, mais une exigence minimale pour une ingénierie logicielle responsable.

Complexité cyclomatique

  • En classe, vous avez vu que la complexité cyclomatique peut être appliquée à l’aide du plugin checkstyle.
  • Ouvrez votre google_checks.xml et assurez-vous que le seuil de complexité cyclomatique est défini à 7 :

    • Recherchez CyclomaticComplexity et assurez-vous que le bloc correspondant est défini comme suit :
      <module name="CyclomaticComplexity">
          <property name="max" value="7"/>
      </module>
      
    • Si Maven signale des erreurs indiquant que votre code est trop complexe, refactorisez le code correspondant afin de réduire la complexité.

Vérification stricte du Javadoc

Votre code doit être documenté à 100 % :

  • Toutes les classes non privées doivent avoir une description JavaDoc
  • Toutes les méthodes non privées doivent avoir une description JavaDoc
  • Si une méthode implémente une interface existante, aucune documentation supplémentaire ne doit être fournie, puisque l’interface est déjà documentée. Placez simplement une annotation @Override avant la signature de la méthode.

Utilisez le plugin Maven javadoc pour vérifier le Javadoc dans votre code (vu en classe / laboratoires).

  • Assurez-vous que la vérification est stricte, c’est-à-dire qu’elle ne génère pas d’avertissements mais des erreurs de compilation.
  • S’il y a des erreurs de compilation, corrigez votre code et ajoutez la documentation manquante.

Couverture de lignes

  • Votre code du contrôleur et du modèle doit atteindre une couverture d’au moins 85 % des lignes de code.
  • Ajoutez le plugin Maven jacoco pour évaluer votre code dans le cadre du processus de build.
  • Assurez-vous que jacoco est strict, c’est-à-dire qu’il rejette la compilation si moins de 85 % des lignes de votre code sont couvertes par des tests.
  • S’il y a des erreurs, étendez vos tests afin d’améliorer la couverture.

À la fin de ce fichier se trouve un exemple de configuration pour le plugin jacoco.

Couverture de mutation

  • Vos tests doivent avoir suffisamment d’assertions pour détecter et éliminer 65 % de tous les mutants.
  • Activez PIT, tel que vu en classe.
  • Assurez-vous que PIT est configuré pour faire échouer le processus de build si moins de 65 % des mutants sont éliminés.
  • Si la compilation échoue, améliorez vos tests afin d’augmenter la couverture de mutation.

Exclure les tests d’intégration

N’appliquez pas PIT aux tests d’intégration. Ils ne passent pas à l’échelle et seront très lents. Consultez le matériel du cours pour savoir comment configurer PIT afin qu’il ne crée des mutations que pour les tests unitaires, en excluant les tests d’intégration.

Code du contrôleur

Jusqu’à présent, vous n’avez implémenté que l’initialisation du jeu (création du modèle) et des actions simples disponibles pour le premier joueur. Dans ce TP, vous implémentez le reste des fonctionnalités du contrôleur afin que votre jeu soit réellement jouable.

  • Aucune nouvelle interface de contrôleur n’a été ajoutée, mais vous pouvez toujours accéder à la documentation existante.
  • Comme vous l’avez vu dans le TP2, la boucle principale du jeu fonctionne comme un échange de type ping-pong entre les joueurs :
    • Le jeu propose des options disponibles.
    • Le joueur sélectionne l’une des options proposées.
  • Ce concept est également appelé « architecture/patron Blackboard » : des options sont proposées sur un tableau, puis une option préférée est sélectionnée.

L’intérêt principal de cette boucle de contrôle est de s’assurer que les joueurs n’ont pas d’accès direct au modèle, ne peuvent pas tricher, et que le jeu respecte toujours les règles correctes.

Architecture Blackboard

L’architecture Blackboard de Skyjo n’est pas très complexe, mais il est important de traduire correctement les règles du jeu en un modèle de diagramme de flux, définissant quelles options sont disponibles à quel moment du jeu.

  • Traduire (ou rétroconcevoir) les règles du jeu en un chemin d’exécution de programme dépasserait le cadre de ce cours de premier cycle.
  • Par conséquent, cet énoncé de TP vous fournit le modèle formel de diagramme de flux afin de vous aider dans votre implémentation.

flowchart TD
    K(Start) --> |**Reveal deck** card| L(Card in buffer)
    L --> |"**Reveal** player card<br>(reject buffer)"| M(Player card replaced or revealed)
    L --> |"**Replace** player card<br>(using buffer)"| M
    M --> |Eliminate<br>col / row| M
    M --> |Cede|O(End)
    K --> |"**Replace** player card<br>(using deck)"| M
Le graphique ci-dessus guide directement votre implémentation : à chaque état (boîte), le joueur courant dispose d’une ou plusieurs options (flèches) :

  • Une flèche peut correspondre à plusieurs instances d’options, par exemple révéler une carte peut s’appliquer à n’importe quelle carte du joueur.
  • L’ordre est important : les options doivent toujours être triées, afin que les tests d’intégration (par exemple en choisissant toujours la première option disponible) suivent de manière fiable le même chemin d’exécution.
  • Concernant l’ordre : s’il y a plusieurs flèches, suivez de gauche à droite. Lorsqu’il y a plusieurs instances d’options, triez-les selon la carte à laquelle elles se réfèrent (de haut en bas, de gauche à droite).

Utilisez les exemples fournis

La définition ci-dessus reste quelque peu formelle, vous pouvez donc chercher des exemples. Heureusement, il y en a plusieurs ! Vous pouvez utiliser les journaux de parties d’exemple fournis pour observer quelles options exactes doivent être proposées à un joueur dans des états de jeu donnés. Voir le lien dans la section suivante.

Tests d’intégration

Deux joueurs robotiques et une série de tests d’intégration sont fournis. Les modifications suivantes à votre base de code sont nécessaires pour les activer :

  • Modifiez votre dépendance d’interface vers la version TP3 : (accès aux joueurs robotiques)

    <dependency>
        <groupId>ca.uqam.info.max</groupId>
        <artifactId>skyjo-interfaces</artifactId>
        <version>tp3-01</version>
    </dependency>
    

  • Modifiez votre dépendance de test vers la version TP3 : (accès aux tests d’intégration)

    <dependency>
        <groupId>ca.uqam.info.max</groupId>
        <artifactId>skyjo-tests</artifactId>
        <version>tp3-01</version>
    </dependency>
    

Séparation des phases de test

  • Par défaut, tous les tests (unitaires et d’intégration) sont exécutés dans la phase test.
  • Utilisez le plugin surefire pour associer l’exécution de tous les tests d’intégration (se terminant par *IT) à la phase verify. Consultez le matériel du cours pour la configuration correspondante du plugin.
  • Configurez le plugin de tests de mutation PIT pour exclure les tests d’intégration. Ils sont plus lents et entraîneront des délais d’expiration. Les tests de mutation doivent tout de même être appliqués aux tests unitaires standards.
  • Étendez la classe DefaultSizeAbstractIT fournie, en fournissant des scénarios de tests d’intégration.
    • Ne copiez pas cette classe, elle est déjà incluse comme dépendance Maven.
    • Étendez-la simplement, c’est-à-dire créez une nouvelle classe dérivée dans votre répertoire test, et implémentez les deux méthodes requises.

Phase verify

  • Vérifiez que les tests d’intégration sont désormais exécutés exclusivement durant la phase verify.
  • Si des tests d’intégration échouent, comparez les traces dans la sortie de vos tests avec les traces fournies à la fin de ce document.
  • Vérifiez itération par itération à quel moment votre jeu Skyjo diverge, et corrigez le code correspondant dans votre contrôleur.

Barème

Critère Pourcentage max
Limite de complexité cyclomatique 7 respectée, aucun avertissement checkstyle 25 %
Tests d’intégration supplémentaires non divulgués réussis 20 %
Couverture de lignes de test > 85 %, testée avec le plugin jacoco 15 %
Couverture de mutation > 65 %, testée avec le plugin PIT 15 %
Tests d’intégration fournis réussis 10 %
Javadoc appliqué dans pom, aucun avertissement 10 %
Dépôt sans encombrement 5 %

Recommandation

Simulez le processus d’évaluation avant la remise : reclonez votre propre projet et exécutez mvn clean verify, puis assurez-vous que votre projet se construit sans avertissements ni erreurs, en utilisant le pom.xml fourni pour ce TP3.

Rejet automatique

Voici une liste de vérification des éléments à éviter à tout prix. Si au moins un élément de cette liste s’applique, votre soumission perdra tous les points liés au code. D’autres critères de rejet automatique peuvent s’appliquer.

  • La solution n’est pas fournie dans le répertoire GitLab dédié, ou n’est pas sur la branche main.
  • La solution est répartie sur plusieurs branches.
  • Le dépôt contient un fichier zip.
  • Le code ne compile pas avec la commande Maven fournie.
  • Le pom.xml contient d’autres plugins ou dépendances que ceux autorisés.
  • Les classes fournies n’implémentent pas les interfaces fournies.
  • L’implémentation des classes de test abstraites n’est pas fournie dans le package de test.
  • La structure du projet a été modifiée / n’est pas respectée.
  • Le programme tente une communication réseau à l’exécution.
  • Le programme bloque à l’exécution.

DIVERS

Cette section fournit des ressources supplémentaires pour la réalisation technique de votre TP.

Parties d’exemple

Les parties d’exemple sont des parties préenregistrées, c’est-à-dire des traces de sessions avec 2 joueurs robotiques ou plus. Vous pouvez utiliser ces exemples pour soit...

  • compléter votre compréhension du diagramme de flux théorique fourni pour l’implémentation de votre contrôleur.
  • identifier des erreurs dans un test d’intégration, en comparant la sortie de vos tests avec la trace attendue des sessions de jeu.

Fichiers

Les traces fournies varient en complexité. Les itérations plus faibles représentent des parties plus courtes (moins de décisions demandées aux joueurs robotiques), tandis que les itérations plus élevées correspondent à des parties plus longues ou plus complexes. Commencez par les parties plus courtes et progressez graduellement vers les plus complexes.

Tous les fichiers dans la grille ci-dessous se réfèrent aux tests d’intégration définis dans le fichier Java DefaultSizeAbstractIT fourni.

Itérations Joueurs Fichier de traces
2 Keksli VS Keksli keksliVsKeksliR42I2.txt
5 Keksli VS Keksli keksliVsKeksliR42I5.txt
2 Keksli VS Keksli keksliVsKeksliR43I2.txt
3 Keksli VS Keksli keksliVsKeksliR43I3.txt
3 MadMax VS MadMax madMaxVsMadMaxR43I3.txt
25 MadMax VS MadMax VS Keksli VS Keksli madMaxVsMadMaxVsKeksliVsKeksliR43I25.txt

Code du lanceur

Les tests ne nécessitent pas de code de lancement, mais il peut être pratique d’avoir un lanceur fonctionnel permettant de jouer manuellement à votre implémentation.
Utilisez le code de lancement ci-dessous pour votre implémentation du TP3. Seules deux modifications du code ci-dessous sont nécessaires :

  1. L’import de votre classe ControllerImpl.
  2. L’utilisation du constructeur de votre ControllerImpl pour créer de nouvelles instances de jeu.
package ca.uqam.info.max.skyjo.view;

import ca.uqam.info.max.skyjo.controller.Command;
import ca.uqam.info.max.skyjo.controller.Controller;
import ca.uqam.info.max.skyjo.controller.ModelPreset;

// TODO: Import YOUR ControllerImpl class here.

/**
 * Launcher for a textual / TTY session with all physical players sharing one keyboard / screen.
 *
 * @author Maximilian Schiedermeier
 */
public class LauncherTp3 {

  /**
   * Default constructor, as imposed by javadoc.
   */
  public LauncherTp3() {
  }

  /**
   * Replace command selector by robot players to obtain an automated game (also used for
   * integration testing).
   */
  private static CommandSelector commandSelector;

  /**
   * Starts game by creating a new controller (which in turn creates a new model). Then keeps
   * prompting players for choices until game end is reached.
   *
   * @param args not used.
   */
  public static void main(String[] args) {

    // Register UI to automatically refresh on model updates
    boolean useTtyColours = true;

    // Create a model, using your model constructor.
    // Make sure your model implements the provided model readonly interface
    String[] playerNames = new String[] {"Max", "Ryan", "Maram", "Quentin"};

    // TODO: Initialize a new game, using YOUR ControllerImpl class here.
    Controller controller = ... // Something like "new ControllerImpl(...)";

    // Register UI to automatically refresh on model updates
    controller.initializeModel(ModelPreset.DEFAULT, playerNames, null);
    controller.addModelObserver(new TextualVisualizer(controller.getModel(), useTtyColours));

    // Register UI to automatically refresh on model updates
    controller.addModelObserver(new TextualVisualizer(controller.getModel(), useTtyColours));

    // Initialize commandSelector for interactive / TTY mode
    commandSelector = new TextualCommandSelector(useTtyColours, false);

    // Play the game :)
    playUntilGameEnd(controller);
  }

  /**
   * Note: This method is not concerned with updating model state representations, for the model
   * adheres to the observer pattern for this purpose.
   * This loop is only about retrieving user inputs until game end. The model is automatically
   * notified and re-rendered after each executed command.
   *
   * @param controller as the MVC controller allowing to progress the game command by command.
   *                   Note that the view has no direct access to the model, and can only
   *                   manipulate model state by executing commands.
   */
  private static void playUntilGameEnd(Controller controller) {

    // Initialize options for game start
    Command[] options = controller.getCurrentPlayerCommands();

    // Keep playing until controller offers no more options (game end)
    while (options.length > 0) {

      // Request a choice from human player - "undo"s have no relevance for INF2050, leave at
      // "false".
      int selectedCommand = commandSelector.selectCommand(options, false);

      // Execute choice (this implicitly re-renders the model)
      controller.doCommand(selectedCommand);

      // Update options
      options = controller.getCurrentPlayerCommands();
    }
  }
}

Jacoco

Voici un exemple de configuration Maven pour le plugin jacoco :

<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.12</version>
    <configuration>
        <excludes>
            <!-- ignore view package, so your launcher is exempted -->
            <exclude>**/view/*</exclude>
        </excludes>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
        </execution>
        <!-- Associate with verify phase, to grab unit AND integration test results.-->
        <execution>
            <id>report</id>
            <phase>verify</phase>
            <goals>
                <goal>report</goal>
            </goals>
        </execution>
        <execution>
            <id>jacoco-check</id>
            <goals>
                <goal>check</goal>
            </goals>
            <configuration>
                <rules>
                    <rule>
                        <element>PACKAGE</element>
                        <limits>
                            <limit>
                                <counter>LINE</counter>
                                <value>COVEREDRATIO</value>
                                <minimum>85%</minimum>
                            </limit>
                        </limits>
                    </rule>
                </rules>
            </configuration>
        </execution>
    </executions>
</plugin>