====== Tests d'intégration et conception ======
Ce TD vise
- à vous apprendre à mettre en place des tests d'intégration
- à concevoir une petite application à plusieurs en prévoyant les tests d'intégration.
/*
4h c'est à dire 2 séances seront consacrées à ce TD.
Vous ne parviendrez peut etre pas à tout faire.
- Soyez itératif, faîtes en sorte de toujours avoir un code qui tourne.
- Ne trainez pas non plus !
*/
===== Partie 1 : Tutoriel EasyMock (1h grand maximum) =====
Si vous dépassez le temps passez à la suite et revenez dessus plus tard.
L'énoncé suivant est issu du "getting started" de [[http://easymock.org/getting-started.html| easyMock]] mis à jour pour travailler avec junit 5 et voir un peu plus de choses.
- Voici le .jar dont vous avez besoin pour avoir accès à l'{{:2019_2020:s3:concprogobjet:td:easymock-4.0.2.jar|environnement EasyMock}}.
- Créer un projet java
- Ajouter à votre **classpath** du projet le .jar donné ci-dessus : sur le nom du projet > Properties > Java Build Path > Puis ajouter le .jar ci-dessus.{{:2019_2020:s3:concprogobjet:td:capture_d_e_cran_2019-11-02_a_18.22.00.png?200|}}
- Voici l'interface dont dépendent vos codes, mais que vous ne devez pas implémenter. Ajoutez la à votre projet.
public interface Collaborator {
// if the document already exists, a "AlreadyAdded" exception is thrown.
// if the document fails to be added, false is return.
public boolean documentAdded(String title) throws AlreadyAdded ;
public void documentRemoved(String title) ;
}
Et l'exception associée : public class AlreadyAdded extends Exception {
}
- Voici vos codes (donc vous pouvez bien sûr les modifier si vous le voulez) et donc il s'agit bien de la classe à tester
public class ClassTested {
public static final String COPY = "_copy";
private Collaborator listener;
private HashMap documents = new HashMap<>();
public void setListener(Collaborator listener) {
this.listener = listener;
}
public void addDocument(String title, String document) {
try {
if (listener.documentAdded(title) )
documents.put(title, document);
} catch (AlreadyAdded e) {
documents.put(title+COPY, document);
}
}
public void removeDocument(String title) {
listener.documentRemoved(title);
}
public Collaborator getListener() {
return listener;
}
public boolean isContained(String key) {
return documents.containsKey(key);
}
}
- Voici un début de tests.
- Remarquez la construction du mock (5.a) import static org.easymock.EasyMock.*;
import static org.junit.Assert.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.easymock.EasyMockSupport;
import org.easymock.Mock;
import org.easymock.TestSubject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class ExampleTest extends EasyMockSupport {
@TestSubject
private ClassTested classUnderTest = new ClassTested(); // 2
@Mock
private Collaborator mock; // 1
@BeforeEach
public void setUp() {
//5.a
mock = mock(Collaborator.class);
classUnderTest = new ClassTested();
classUnderTest.setListener(mock);
}
- Vous pouvez l'exécuter il ne se passe rien (il n'y a aucun test 8-) )
- Ajoutez un test. @Test
public void testRemoveNonExistingDocument() {
// This call should not lead to any notification
// of the Mock Object
replay(mock); // 6.b
classUnderTest.removeDocument("Does not exist");
}
- Il échoue en effet on enregistre (6.b) que le mock ne reçoit aucun message alors que là il a reçu un message. Vous devrez donc avoir l'erreur suivante java.lang.AssertionError:
Unexpected method call Collaborator.documentRemoved("Does not exist"):...
- Ajoutez un test, cette fois-ci juste :
- On déclare le message que doit recevoir le mock (7.a)
- On enregistre (7.b)
- On lance le test sur notre code (7.c)
- On explicite la vérification (réalisée par défaut) (7.d) @Test
public void testRemoveNonExistingDocument2() {
mock.documentRemoved("Does not exist"); //7.a
replay(mock); //7.b
classUnderTest.removeDocument("Does not exist"); //7.c
verify(mock); //7.d
}
- Ajoutez un test, mais cette fois-ci nous explicitons la valeur de retour attendue et nous créons deux mocks pour nous assurer qu'il n'y a pas d'effets secondaires
@Test
public void testAddDocument() throws AlreadyAdded {
//Initialisation
Collaborator firstCollaborator = mock(Collaborator.class);//construction de mock
Collaborator secondCollaborator = mock(Collaborator.class);//construction de mock, il ne sera pas utilisé
classUnderTest.setListener(firstCollaborator);
//// expect document addition
expect(firstCollaborator.documentAdded("New Document")).andReturn(true); //On attend que le mock réponde True !
replayAll(); //on enregistre le comportement de tous les Mocks
classUnderTest.addDocument("New Document", "content");
assertTrue(classUnderTest.isContained("New Document")); //on vérifie que NOS codes se comportent correctement
verifyAll(); //On vérifie le comportement de tous les mocks
}
- Ajoutez un test, mais cette fois-ci nous attendons pour valeur de retour false @Test
public void testFailingAddDocument() throws AlreadyAdded {
//Initialisation
Collaborator firstCollaborator = mock(Collaborator.class);
Collaborator secondCollaborator = mock(Collaborator.class);
classUnderTest.setListener(firstCollaborator);
//// expect document addition
expect(firstCollaborator.documentAdded("New Document")).andReturn(false);
replayAll();
classUnderTest.addDocument("New Document", "content");
assertFalse(classUnderTest.isContained("New Document"));
verifyAll();
}
- et enfin nous testons une levée d'exception @Test
public void testDuplicationWhenAddingDocument() throws AlreadyAdded {
//Initialisation
Collaborator firstCollaborator = mock(Collaborator.class);
Collaborator secondCollaborator = mock(Collaborator.class);
classUnderTest.setListener(firstCollaborator);
//// expect document addition
expect(firstCollaborator.documentAdded("New Document")).andThrow(new AlreadyAdded());
replayAll();
classUnderTest.addDocument("New Document", "content");
assertTrue(classUnderTest.isContained("New Document"+ClassTested.COPY));
verifyAll();
}
Cela conclut la partie tutorielle.
A présent, vous devriez savoir créer vous même des tests d'intégration.
===== Partie 2 : Un jeu de Quizz =====
Vous devez réaliser en groupe de X étudiants un jeu de quizz dont voici la spécification.
==== Spécification ====
* **V0 : Version de base**
- En tant que joueur, je veux répondre à une question du quizz
- Si j'ai la bonne réponse je gagne 1pt
- Si je n'ai pas la bonne réponse, la bonne réponse m'est donnée.
- La vérification de la réponse ne dépend pas des minuscules ou majuscules
- ex :
- "Quelle est la capitale de la France", je réponds PARIS, le jeu me félicite et m'annonce que j'ai gagné 1 pt
- "Quelle est la capitale de l'Espagne", je réponds madrid ... j'ai gagné 1 pt
- "Quelle est la capitale de l'Érythrée", je réponds Assab, le jeu m'annonce que la bonne réponse est Asmara, je n'ai pas gagné de points
- En tant que joueur, je veux jouer une partie de quizz, de façon à m'amuser en vérifiant mes connaissances.
==== Modélisation ====
=== Exigences ===
Voici les interfaces qui vous sont données et que vous devez respecter.
public interface GameInterface {
void startGame();
boolean isNotOver();
String nextQuestion();
boolean setCurrentUserAnswer(String answer);
String getRightAnswer();
int getPoints();
}
public interface QuizInterface {
public String getQuestion(int currentQuestionNumber) ;
public boolean isGoodAnswer(int currentQuestionNumber, String answer);
public String getAnswer(int currentQuestionNumber);
public int size();
public void addQuestion(QuestionInterface q1);
}
public interface QuestionInterface {
String getAnswer();
String getQuestion();
boolean isGoodAnswer(String answer);
}
**Voici un code pour tester un jeu.**
package quizzPK;
import java.util.Scanner;
public class mainTestGame {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
QuizInterface quiz = new Quiz();
QuestionInterface q1 = new Question("Capitale de la France ?", "Paris");
QuestionInterface q2 = new Question("Capitale de l'Allemagne ?", "Berlin");
QuestionInterface q3 = new Question("Capitale de l'Italie", "Rome");
quiz.addQuestion(q1);
quiz.addQuestion(q2);
quiz.addQuestion(q3);
GameInterface game = new Game(quiz);
game.startGame();
while (game.isNotOver()) {
System.out.println(game.nextQuestion() + " : ") ;
String answer = sc.nextLine();
boolean valid = game.setCurrentUserAnswer(answer);
if (!valid ) {
System.out.println("You failed, the correct answer is : " + game.getRightAnswer() );
}
else
System.out.println("Well done ! ");
}
System.out.println("Score : " + game.getPoints());
}
}
=== A Faire ===
- **Modélisez** par groupe de 2 ou 3 étudiants, le jeu
- En particulier, intéressez-vous aux interactions entre les classes.
- Une fois que vous êtes d'accord, vous passez à la suite
- **Développez** séparément
- Etudiant 1 : Développez la classe ''Game'' sans développer la classe ''Quiz''.
- Tester votre classe ''Game'' en utilisant des Mocks.
- Etudiant 2 : Développez la classe ''Quizz'' sans développer la classe ''Question''.
- Tester votre classe ''Quizz'' en utilisant des Mocks
- Intégrez vos codes.
L'essentiel n'est pas d'atteindre cette ultime étape. Mais davantage d'apprendre à écrire des tests d'intégration.
/*
=== Headline ===
- 10 questions sont posées au joueur
- Seules des questions auxquelles le joueur n'a jamais répondu juste lui sont posées.
- S'il n'y a plus assez de questions "nouvelles", le joueur est invité à charger d'autres questions.
- La partie est enregistrée à la fin de la partie, i.e. que si on interrompt la partie en cours de jeu, elle n'est pas enregistrée.
- ex :
- Je lance une partie à mon nom
- 10 questions me sont posées et je réponds juste à 4 questions
- A la fin de la partie le système me signale que j'ai gagné 4 points et que la partie est enregistrée sous mon nom.
- En tant que joueur, je veux consulter les différentes parties que j'ai réalisées, de façon à vérifier ma progression PRIORITE FAIBLE
- Je peux voir les dates de parties, le nombre de points marqués par partie.
- ex.
- Etant donné que J'ai joué 5 parties....
- Je visualise les 5 parties avec leur date et le nombre de points marqués.
- Je peux en savoir plus sur une partie : durée de la partie, et questions posées et mes réponses ....
- En tant que producteur de questions, je veux pouvoir ajouter des questions à la base de questions PRIORITE MOYENNE
- Une question est composée d'une question et d'une réponse.
- On ne vérifie pas si la question existe déjà, on suppose que le producteur sait ce qu'il fait.
- ex.
- Etant donné que dans ma base, il y a déjà 3 questions.
- J'ajoute la question "Praia est la capitale de quel pays?" réponse Capt Vert
- Il y a 4 questions dans la base à présent dont ma nouvelle question.
- En tant que joueur, je veux pouvoir charger de nouvelles questions PRIORITE MOYENNE
- ex.
- Etant donné que j'ai déjà 3 questions dans ma base.
- Je charge la nouvelle série de questions datée du 27/10
- J'ai maintenant toutes les questions que j'avais avant plus les nouvelles questions.
- Attention les références aux questions ne sont pas perdues, i.e. que si j'ai déjà répondu à la question sur "la capitale de Paris", la question ne me sera pas reposée.
* V1A : Plusieurs joueurs
* V1B : Des catégories de questions.
* V1C : Des difficultés de questions.
* V1D : Persistance : les questions sont stockées dans un fichier, de même pour les parties.
*/
/* ==== A faire ====
- Conception : V0 (Environ 1/2 heure intense)
- Conseils
* Concevez l'application V0 à deux (i.e. UC, Classes, un diagramme de séquence pour analyser le déroulement d'une partie est conseillé, MAIS ne faîtes que ce qui vous paraît vraiment utile)
* Partagez le travail à réaliser
* Prévoyez les tests à réaliser pour chacune des histoires, vous pouvez même faire du TDD
- //**Enrichissez** les histoires si besoin pour être sûrs de savoir les tester.//
- 8-O Sauvegardez une copie de votre conception quelle qu'elle soit : photo ou autre. Vous utiliserez cette conception pour faire le point à la fin entre votre travail initial et la réalisation. Vous ne serez pas noté dessus, vous devez apprendre à vous évaluer.
- Vous pouvez à partir de là choisir de focaliser sur une histoire ou de traiter plusieurs histoires en même temps.
- Développement : V0
* Développez le jeu en testant chacun individuellement vos classes et en utilisant les mocks pour les interactions.
- Intégration : V0
* Mettez vos codes en commun
* Testez une histoire et vérifier que vous pouvez dire qu'elle est terminée, c'est à dire que tous les tests passent.
* Faîtes les tests sur toutes les histoires, soyez itératif.
- 8-O Faîtes une copie de votre architecture;
- vous la comparerez avec la précédente et la suivante. Il s'agit ici pour vous de
- déterminer si vous aviez fait des erreurs ou des incomplétudes,
- déterminer si vous saurez faire mieux la prochaine fois ou non, qu'avez-vous appris éventuellement?
- identifier les points que vous aimeriez améliorer (liste des TODO dans le code, le modèle, l'architecture).
- V1 : Choisissez une extension, complétez éventuellement les histoires et allez jusqu'au code correspondant.
- 8-O reprendre la question précédente.
- Si vous êtes ici et que vous en avez envie ajoutez une autre extension.
*/
/*
==== Quelques trucs utiles ====
Pour Interroger vous pouvez utiliser:
import javax.swing.JOptionPane;
....
String m = JOptionPane.showInputDialog("Anyone there?");
System.out.println(m); // juste pour expliquer ici
JOptionPane.showMessageDialog(null, "A oui, il y a quelqu'un");
*/
/*
==== A RENDRE ====
- Document
- faisant état de vos réponses aux points 8-O ; les derniers modèles seront considérés pour l'évaluation de l'architecture.
- d'une image de la couverture de tests finale
- Précisez les extensions réalisées.
- Des codes et tests associés.
Rendus au plus tard le lundi 19 novembre à 18h45 sous http://jalon.unice.fr/cours/blay/Cours-blay-20150930110548/BoiteDepot-blay-20181113101132476393?mode_etudiant=true&tab=deposit
Voici le document qui sera utilisé pour l'évaluation (il peut encore changer) : https://goo.gl/forms/PmNPOPcurIKJLlo32
*/