====== 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 */