Skip to content

TP 3

Controller finalization, integration tests and advanced code quality requirements.

Meta

  • Deadline: Sunday Apr 12th, 11:59 PM
  • Submission: GitLab
  • Teams: Triplets of students, as announced in class
  • Late submission: Not possible. You will be graded by whatever contents is on your main branch at the deadline

It is your responsibility to submit code in a timely manner. Do not wait until the last minute, GitLab may fail.

Learning goals

In this third TP you will learn how to apply advanced code-quality requirements on existing and new code. In addition, you learn how to use robotic players as integration tests, to guide a correct realization of game rules.

More concretely, for the third TP you will...

  1. complete your Skyjo Controller implementation, and integrate advanced concepts to ensure code quality into the build process. All code quality requirements must by enforced via pom.xml.

  2. Your code must be tested with classic and mutation tests. You must ensure a requested coverage and mutation coverate is met.

  3. Your code must comply to industry grade code formatting rules, and respect a requested complexity threshold.
  4. Your code must be fully documented with JavaDoc comments for all public methods and classes.

  5. use provided integration tests to guide the completion of your controller implementation, i.e. implementation of game rules.

  6. Your game implementation must support all game actions correctly, in any game situation.

  7. Provided decision-makers (deterministic robot players) will serve as grounds to verify if your model and controller implementation correctly implement the rules. These robotic players are used within provided integration tests. If an integration test fails, you must compare the expected and factual game execution paths to identify and fix potential implementation errors.

Step-by-step overview

  1. Corrections: Check your previous submission.
    • Use the feedback returned to you on GitLab (may still take a few days, you can already re-run your tests).
    • Fix any issues, especially failing tests. Use the debugger.
  2. Code quality:
    • Enable cyclomatic complexity threshold of 7, via google_checks.xml and pom.xml.
    • Enable strict javadoc verification via pom.xml.
    • Raise code line coverage to 85% via pom.xml.
    • Enable mutation testing, enable a minimum mutation test coverage via pom.xml.
  3. Controller code: Complete your controller implementation, so the game is fully playable.
  4. Integration tests:
    • Separate integration tests from regular tests using the *IT suffix via pom.xml.
    • Extend the provided abstract integration test class to enable integration tests, and make sure all integration tests pass.

You can interleave 3. & 4.

Integration tests are also an efficient means to guide implementation (see TDD), i.e. you can first enable integration tests, and then gradually finalize your codebase, until all integration tests run through. The provided integration tests are also gradual, i.e. you can start with a short integration test that only plays a few turns, and later proceed to the more complex versions.

Detailed instructions

Several changes are necessary to pass this third milestone. In the following comes a detailed breakdown of what is expected.

Corrections

  • Double check you have extended all required tests of the TP2 milestone.
  • Verify your implementation checks all previous tests.
  • As soon as you receive individual feedback for your TP2 submission, make sure to address all issues before adding new functionality.

Code quality

For this TP3 your code must comply to various code quality metrics.

  • These are not options. It is not sufficient to manually check these in your IDE.
  • They must be enforced as maven plugins.
  • The plugins must not be configured to only emit warnings, i.e. building must fail if there are issues in your code.

Your submission will be rejected if code quality measures have been ignored. This is not a luxury, but a minimum requirement for accountable software engineering.

Cyclomatic complexity

  • In class, you've seen that cyclomatic complexity can be enforced using the checkstyle plugin.
  • Open your google_checks.xml and make sure the cyclomatic complexity threshold is set to 7:

    • Search for CyclomaticComplexity and make sure the corresponding block is set to:
      <module name="CyclomaticComplexity">
          <property name="max" value="7"/>
      </module>
      
  • If maven raises errors about your code being too complex, refactor the corresponding code to mitigate complexity.

Strict javadoc verification

Your code must be 100% documented:

  • All non-private classes must have a JavaDoc description
  • All non-private methods must have a JavaDoc description
  • If a method implements an existing interface, no additional documentation should be provided, as the interface is already documented. Simple place an @Override annotation before the method signature.

Use the javadoc maven plugin to check javadoc in your code. (seen in class / labs)

  • Make sure the check is strice, i.e. it does not raise warnings but build errors.
  • If there are build errors, fix your code and add the missing documentation.

Line coverage

  • Your controller and model code must reach a coverage of at least 85% lines of code.
  • Add the jacoco maven plugin (seen in class / labs) to assess your code as part of the build process.
  • Make sure jacoco is strict, i.e. rejects builds if less than 85% of your code lines are covered by tests.
  • If there are errors, extend your tests to improve coverage.

Mutation coverage

  • Your tests must not have sufficient assertions to detect and kill 65% of all mutants.
  • Enable PIT, as seen in class.
  • Make sure PIT is configured to fail the build process if less than 65% of mutants are killed.
  • If the build fails, improve your tests to mutant coverage augments.

Exclude integration tests

Do not apply PIT on integration tests. They do not scale and will be very slow. Search the course material for how to configure PIT so it only creates mutations for unit tests, but excludes integration tests.

Controller code

So far you've only implemented game initializations (model creation) and simple actions available to the first player. In this TP you implement the remaining controller functionality, so your game is actually playable.

  • No new controller interfaces have been added, but you can still access the existing documentation.
  • As you've seen in the TP2, the game's main control loop plays a ping-pong game with players:
    • The game offers available options.
    • The player selects one of the offered options.
  • This concept is also referred to as "Blackboard architecture / pattern": Options are proposed on a blackboard, afterward a preferred option is selected.

The main interest of this control loop is to ensure players have no direct access to the model, cannot cheat, and the game is guaranteed to follow the correct game rules.

Blackboard architecture

Skyjo's blackboard architecture is not very complex, but it is important to correctly translate the game's rules into a flowchart model, defining which options are available at which moment of a game.

  • Translating (or reverse engineering) game rules into a program execution path would be beyond the scope if this undergrad course.
  • Therefore, this TP handout provides you with the formal flowchart model, to help you with your implementation.
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

The above graph directly guides your implementation: At any state (box), there the current player has one or multiple options (arrows):

  • One arrow can translate to multiple option instances, e.g. revealing a card can be applied to any player card.
  • Order is important: Options must always be sorted, so the integration tests (e.g. always picking the first available option) reliably take the same execution path.
  • Concerning order: If there are multiple arrows, go from left to right. When there are multiple option instances, sort them based on the card they refer to (top left to bottom right).

Use the provided samples

The above definition is still somewhat formal, so you might seek for examples. Luckily there are plenty! You can use the provided sample game logs to inspect which exact options must be offered to a player at exemplary game states. See link in next section.

Integration tests

Two robotic players and a series of integration tests are provided. The following changes to your codebase are necessary to activate them:

  • Change your interface dependency to the TP3 version: (access to robot players)
<dependency>
    <groupId>ca.uqam.info.max</groupId>
    <artifactId>skyjo-interfaces</artifactId>
    <version>tp3-01</version>
</dependency>
  • Change your test dependency to the TP3 version: (access to integration tests)
<dependency>
    <groupId>ca.uqam.info.max</groupId>
    <artifactId>skyjo-tests</artifactId>
    <version>tp3-01</version>
</dependency>

Test phase splitting

  • By default, all tests (unit and integration) are executed in the test phase.
  • Use the surfire plugin to bind execution of all integration tests (ending with *IT) to the verify phase. Search the course material for the corresponding plugin configuration.
  • Configure the PIT mutation test plugin to exclude integration tests. They are slower and will lead to timeouts. Mutation testing must still be applied to standard unit tests.
  • Extend the provided DefaultSizeAbstractIT class, providing integration test scenarios.
    • Do not copy paste this class, it is already included as a maven dependency.
    • Just extend it, i.e. create a new extending class in your test directory, and implement the two required methods.

Verify phase

  • Verify integration test are now exclusively executed during the verify phase.
  • If integration tests fail, compare the traces in your test output to the provided traces at the end of this document.
  • Verify iteration by iteration where your skyjo game diverges, and fix the corresponding code in your controller.

Grading scheme

Criteria Max percentage
Cyclomatic complexity limit 7 respected, no checkstyle warnings 25%
Undisclosed additional integration tests pass 20%
Test line coverage > 85%, tested with jacoco plugin 15%
Mutation coverage > 65%, tested with PIT plugin 15%
Provided integration tests pass 10%
Javadoc enforced in pom, no warnings 10%
Repo clutter free 5%

Recommendation

Simulate the grading process before submission: Re-clone your own project and run mvn clean verify and ensure your project can be built without warnings or errors, using the pom.xml provided for this TP3.

Desk rejection

Here is a checklist of what to avoid at all costs. If at least one item of the checklist applies, your submission will lose all code-related points. There may be other criteria for desk rejection.

  • Solution not provided in dedicated GitLab directory, or not on main branch.
  • Solution is distributed across multiple branches.
  • Repository contains zip file.
  • Code does not compile with provided maven command.
  • pom.xml has other plugins or dependencies than allowed.
  • Provided classes do not implement provided interfaces.
  • Implementation of abstract test classes is not provided in test package.
  • Project structure has been altered / is not respected.
  • Program attempts network communication at runtime.
  • Program stalls at runtime.

MISC

This section provides additional resources for the technical realization of your TP.

Sample games

Samples games are pre-recorded games, i.e. traces of sessions with 2 or more robotic players. You can use those samples to either...

  • complement your understanding of the theoretic flowchart provided for your controller implementation.
  • identify errors in an integration tests, by comparing your test output to the expected game sessions trace.

Files

The provided traces vary in complexity. Lower iterations represent shorter games (fewer decisions requested from robot players), whilst higher iterations are either extended, or more complex sample games. Start with shorter games and gradually work your way to the more complex ones.

All files in below grid refer to integration tests defined in the provided DefaultSizeAbstractIT java file.

Iterations Players Traces file
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

Launcher Code

Testing does not require a launcher code, however it may be convenient to have a functioning launcher, that allows manually playing your implementation.
Use the below launcher code for your TP3 implementation. Only two changes to the code below are needed:

  1. import of your ControllerImpl class.
  2. Using your ControllerImpl constructor to create new game instances.
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();
    }
  }
}