Commit 64d07bf6 authored by Erwan BOUSSE's avatar Erwan BOUSSE

Améliorations cours testabilité

parent 845b36e2
Pipeline #21399 passed with stages
in 12 minutes and 22 seconds
......@@ -3,7 +3,7 @@
include::commons.adoc[]
// end::selfcontained[]
:imagesdir: ../resources/img
:sourcedir: ../resources/snippets
:sourcedir: ../resources/code
== Présentation
......@@ -33,9 +33,6 @@ Et si la testabilité d’un logiciel est importante, alors il sera plus simple
- de les exécuter et de les re-exécuter (et obtenir les mêmes résultats !),
- et donc de découvrir des défauts !
// |note: exécuter et re-exécuter: _répétabilité_
// |===
== Facteurs de testabilité
......@@ -70,13 +67,44 @@ NOTE: L’observation est la plupart du temps *partielle* : il est rare d'avoir
La *principale difficulté* de l'observabilité est qu'il est parfois difficile d'améliorer l'observabilité sans nuire à la conception et/ou à l'implémentation.
D'une part, fournir un accès à l'état interne du logiciel peut aller _à l'encontre du principe d'encapsulation_, pourtant fondamental en programmation orientée objet.
D'autre part, il faut garantir que les méthodes permettant d'accéder à l'état du logiciel (ex. _getters_) _n'entrainent pas d'effet de bord_.
C'est un problème classique en physique quantique – Heisenberg – on ne peut pas connaître à la fois la vitesse et la position d’une particule parce que les observer les modifie !
D'autre part, il faut garantir que les méthodes permettant d'accéder à l'état du logiciel (ex. getters) _n'entrainent pas d'effet de bord_.
C'est un problème classique en physique quantique : on ne peut pas connaître à la fois la vitesse et la position d’une particule parce que les observer les modifie !
.Jeu de morpion peu observable
====
On considère un petit jeu de morpion suivant :
[source, java, linenums]
----
include::{sourcedir}/tictactoe/TicTacToeGame.java[]
----
Une instance de `TicTacToeGame` offre comme services :
. L'affichage de la grille dans son état courant via `printGrid`,
. La possiblité pour le joueur courant de jouer dans une case via `play`.
Ainsi, la seule manière pour un utilisateur de cette classe de savoir ce qu'il se passe est l'affichage de la grille dans la console.
Cela pose de nombreux problèmes d'observabilité :
- Un affichage console n'est pas facilement programmatiquement analysable.
Il est à minima possible de récupérer une `String` contenant tout ce qui a été affiché dans la cnosole, mais cela implique rediriger tout le flux `System.out` dans une variable, ce qui n'est ni souhaitable ni facile.
- Même si on récupérer le contenu de la grille dans une `String`, il va être nécessaire d'analyser et décomposer cette `String` pour en déduire quel est l'état du jeu en terme de cases occupées ou bien en terme de joueur courant (en comptant le nombre de coups de chaque joueur).
Il est donc difficile de vérifier que les bons coups sont joués au bon endroit, ou bien que le bon joueur est en train de jouer.
- Même si on réussit à analyser le contenu de la grille textuelle, lors de l'état initial du jeu, il n'est pas possible de savoir quel est le joueur courant.
On ne peut donc pas vérifier facilement que le premier joueur est bien choisi aléatoirement.
Tous ces problèmes sont causés par la conception logicielle du jeu : l'encapsulation est maximale et ne sont offertes publiquement que les deux opérations essentielles au fonctionnement du jeu.
Par conséquent, toutes les informations internes sont cachées au sein de membres privés, à savoir le joueur courant et l'état de la grille.
Bien qu'il soit argumentable qu'une API épurée soit plus facilement maintenable ou durable dans le temps, ici cela rend l'écriture d'oracles très difficile.
Pour améliorer cela, on pourrait avant tout imaginer des opérations publiques sûres permettant d'accéder au joueur courant et à la grille.
Pour aller plus loin, on pourrait également stocker le dernier coup joué et rendre également ce dernier coup accessible publiquement.
====
//TODO exemples de bonne observabilité
//TODO exemples de mauvaise observabilité
=== Contrôlabilité
......@@ -94,6 +122,22 @@ Idéalement il faut pouvoir contrôler :
- ainsi que l'*environnement* dans lequel le logiciel s'exécute (fichiers, bases de données, composants logiciels externes avec lesquels il interagit)
.Jeu de morpion peu contrôlable
====
On considère le même jeu de morpion que celui présenté dans l'exemple précédent.
On peut remarquer que la contrôlabilité de ce logiciel est très limitée, car il est seulement possible de créer une grille dans un état initial, puis faire joueur les joueurs un par un.
Ainsi, si on souhaite un scénario de test visant à vérifier le comportement du jeu lorsque la grille est dans un état particulier, la seule manière d'atteindre cet état est d'effectuer de multiples appels à `play`.
On peut faire la même remarque pour ce qui est du joueur courant.
Afin d'améliorer la contrôlabilité, on peut ensisager des opérations pour facilement et exceptionnellement modifier les données privées encapsulées.
Une possibilité serait d'ajouter de nouveaux constructeurs permettant de créer une partie qui débuterait dans un état choisi.
Ainsi on ne pourrait pas "tricher" dans une partie déjà commencée, mais on pourrait débuter une partie dans l'état souhaiter à des fins de test.
====
// '''''
......@@ -129,7 +173,6 @@ Idéalement il faut pouvoir contrôler :
//TODO exemples de bonne contrôlabilité
//TODO exemples de mauvaise contrôlabilité
......@@ -165,6 +208,16 @@ En ce qui concerne le _logiciel_ testé, bien qu'une forme élémentaire de disp
.Jeu de morpion plutôt disponible
====
On considère le même jeu de morpion que celui présenté dans les exemples précédents.
Le code source est totalement disponible, ainsi que sa spécification en JavaDoc.
On pourrait souhaiter une spécification système un peu plus complète, mais à part cela la disponibilité est satisfaisante.
====
//TODO exemples de bonne disponibilité
//TODO exemples de mauvaise disponibilité
......@@ -183,6 +236,21 @@ Or, si il est possible de finement manipuler le logiciel comme on l'entend, alor
//TODO exemples de mauvaise stabilité
.Jeu de morpion pas très stable
====
On considère le même jeu de morpion que celui présenté dans les exemples précédents.
Malheuremsent, intéragir deux fois exactement de la même manière avec le jeu ne fournira pas toujours le même résultat, car *le premier joueur est déterminé aléatoirement* ! Il y a donc un problème de stabilité.
Pour y remédier, on peut se tourner vers une amélioration de la _contrôlabilité_, avec au moins deux options possibles :
- Soit donner la possibilité de rentre l'aléatoire déterministe, en fixant la _seed_ du générateur d'aléatoire.
- Soit de permettre tout simplement de choisir le joueur de départ arbitrairement, à des fins de test.
====
== Construire des logiciels testables
La meilleure manière de favoriser la testabilité des logiciels est de prendre en compte ce problème *dès la conception*, notamment en ce qui concerne la contrôlabilité et l’observabilité.
......
public class ExampleMain {
public static void main(String[] args) {
TicTacToeGame game = new TicTacToeGame();
game.printGrid();
game.play(0, 0);
game.printGrid();
game.play(0, 1);
game.printGrid();
}
}
import java.util.Random;
public class TicTacToeGame {
private final PLAYER[][] gridOccupancy;
private PLAYER currentPlayer;
private enum PLAYER {
CROSS('x'), CIRCLE('o'), NONE('-');
private char symbol;
private PLAYER(char symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return this.symbol + "";
}
}
private static PLAYER[][] createInitialOccupancy() {
PLAYER[][] result = new PLAYER[3][3];
for (int i = 0; i < result.length; i++) {
for (int j = 0; j < result[0].length; j++) {
result[i][j] = PLAYER.NONE;
}
}
return result;
}
/**
* Creates a new game of TicTacToe. The first player is random.
*/
public TicTacToeGame() {
this.gridOccupancy = createInitialOccupancy();
this.currentPlayer = new Random().nextBoolean() ? PLAYER.CIRCLE : PLAYER.CROSS;
}
/**
* Prints the current grid in text format. Example:
*
* --o
* --o
* -x-
*
*/
public void printGrid() {
System.out.println("Grid :");
for (int i = 0; i < this.gridOccupancy.length; i++) {
for (int j = 0; j < this.gridOccupancy[0].length; j++) {
System.out.print(gridOccupancy[i][j]);
}
System.out.println();
}
}
/**
* Plays a move as the current player, then switches to the other player. Does
* nothing if the action is invalid (out of bounds or occupied cell).
*
* @param x The x coordinate.
* @param y The y coordinate.
*/
public void play(int x, int y) {
if (x >= 0 && x <= 2 && y >= 0 && y <= 2 && gridOccupancy[x][y] == PLAYER.NONE) {
this.gridOccupancy[x][y] = this.currentPlayer;
this.currentPlayer = this.currentPlayer == PLAYER.CROSS ? PLAYER.CIRCLE : PLAYER.CROSS;
}
}
}
\ No newline at end of file
:stem:
= Test Logiciel − Testabilité et doublures
include::commons.adoc[]
:imagesdir: ../resources/img
:sourcedir: ../resources/snippets
:revdate: aaa
[.subtitle]
Comment choisir ce que l'on teste ?
[.authors]
****
[.author]
--
[.name]
Erwan Bousse
[.institution]
Université de Nantes
--
****
[.vspace-40]
_Dernière révision : {revdate}_
== Contenu de cette partie
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment