From c330379555b733e3ca03c16bae98e7e3d2c4d003 Mon Sep 17 00:00:00 2001 From: Thomas Clavier <tom@tcweb.org> Date: Thu, 8 Feb 2024 22:05:03 +0100 Subject: [PATCH] avant le cours --- 03-tests.md | 969 ++++++++++++++++++ README.md | 5 +- sample-code/pom.xml | 60 ++ .../java/fr/univlille/iut/info/r402/Cell.java | 4 + .../univlille/iut/info/r402/CellContent.java | 5 + .../fr/univlille/iut/info/r402/Ocean.java | 14 + sample-code/src/test/java/OceanTest.java | 16 + 7 files changed, 1071 insertions(+), 2 deletions(-) create mode 100644 03-tests.md create mode 100644 sample-code/pom.xml create mode 100644 sample-code/src/main/java/fr/univlille/iut/info/r402/Cell.java create mode 100644 sample-code/src/main/java/fr/univlille/iut/info/r402/CellContent.java create mode 100644 sample-code/src/main/java/fr/univlille/iut/info/r402/Ocean.java create mode 100644 sample-code/src/test/java/OceanTest.java diff --git a/03-tests.md b/03-tests.md new file mode 100644 index 0000000..ce31b55 --- /dev/null +++ b/03-tests.md @@ -0,0 +1,969 @@ +--- +author: Thomas Clavier +title: Il est beau mon test +--- + +# Programme parfait ? + +C'est quoi un programme parfait ? + +# Selon XP + +* DRY (Don't Repeat Yourself) +* Simple +* Du code lisible +* Tous les tests passent + +# DRY + +Une information dupliquée c'est le meilleur moyen pour avoir une désynchronisation plus tard. + +## Information dupliquée ? +Pour vous, dans quels cas a-t-on une information dupliquée ? + +. . . + +* Copier / Coller +* Fichier de configuration / Code + +## Resource vs Java + +```java +FileReader readerUtilisateur = new FileReader( getClass() + .getClassLoader() + .getResource("utilisateurs.json").getFile() ); +List<Utilisateur> utilisateurs = JsonbBuilder.create() + .fromJson(readerUtilisateur); +UtilisateurDao utilisateurDao = BDDFactory.buildDao(UtilisateurDao.class); +if (! tableExist("utilisateurs") { + utilisateurDao.createTable(); + for ( Utilisateur utilisateur: utilisateurs) { + utilisateurDao.insert(utilisateur); + } +} +``` +```json +[ + {"id": "1", "role": "admin", "nomPrenom": "Juliette", "motDePasse": "1234"}, + {"id": "2", "role": "presta", "nomPrenom": "Toto", "motDePasse": "1234"}, + {"id": "3", "role": "client", "nomPrenom": "Richard", "motDePasse": "1234"} +] +``` + +## Resource vs Java + +```java +UtilisateurDao utilisateurDao = BDDFactory.buildDao(UtilisateurDao.class); +if (! tableExist("utilisateurs") { + utilisateurDao.createTable(); + utilisateurDao.insert(new Utilisateur(ADMIN, "Juliette", "1234")); + utilisateurDao.insert(new Utilisateur(PRESTA, "Toto", "1234")); + utilisateurDao.insert(new Utilisateur(CLIENT, "Richard", "1234")); +} +``` + +## Complexité accidentelle + +C'est la complexité introduite dans des programmes informatiques non en raison de la complexité du problème, mais de manière accidentelle en raison des choix de développement. + +* Fichier de configuration vs code : oui si vos utilisateurs ne savent/peuvent pas éditer le code +* Fichier de données vs code : oui si c'est plus facile à mettre à jour (masse, données venant d'un autre système, etc.) + +# Simple + +* KISS (Keep It Simple Stupide) +* YAGNI (You Aren't Gona Need It) + +## KISS <small>Simplicité technique</small> + +```java +public static double average(List<Double> numbers) { + Double sum = 0.0; + for (Double number : numbers) { + sum += number; + } + if (numbers.size() > 0) + return sum / numbers.size(); + return 0.0; +} +``` + +```java +public static double average(List<Double> numbers) { + return numbers.stream() + .mapToDouble(Double::doubleValue) + .average() + .orElse(0.0); +} +``` + +## SOLID + +* *S*ingle responsability +* *O*pen/closed +* *L*iskov substitution +* *I*nterface segregation +* *D*ependency inversion + +## Loi de Demeter + +```java +public class Ocean { + private final Cell[][] internalRepresentation; + + public Cell[][] getInternalRepresentation() { + return internalRepresentation; + } +} +``` + +```java +Ocean pacific = new Ocean(3,3); +pacific.getInternalRepresentation()[0][0] = new Cell(Fish); +``` +. . . + +vs + +```java +public class Ocean { + public Ocean putCellContentAt(CellContent content, int line, int col) { + internalRepresentation[line, col] = new Cell(content); + return this; + } +} +``` + +```java +Ocean pacific = Ocean.of(3,3).putCellContentAt(Fish,0,0); +``` + + + +## YAGNI <small>Simplicité fonctionnelle</small> + +Et si un jour il faut configurer une semaine avec plus que 7 jours ? + +```json +[ + {"id": "1", "name": "lundi"}, + {"id": "2", "name": "mardi"}, + {"id": "3", "name": "mercredi"}, + {"id": "4", "name": "jeudi"}, + {"id": "5", "name": "vendredi"}, + {"id": "6", "name": "samedi"}, + {"id": "7", "name": "dimanche"}, +] +``` + +. . . + +C'est arrivé 1 fois depuis la création du calendrier grégorien en 525 ! Même si la semaine de 7 jours existe depuis Babylone en -1595. + +# Lisible + +C'est quoi un code lisible ? + +## Lisible ? + +```java +public void init() { + // Paramétrage + Properties properties = new Properties(); + properties.setProperty("foreign_keys", "true"); + + // Connection à la base de données + Jdbi jdbi = Jdbi.create(basePath + "data.db", properties) + .installPlugin(new SQLitePlugin()) + .installPlugin(new SqlObjectPlugin()) + .registerArgument(new UUIDArgumentFactory()); + + // Initialisation avec un admin + if (!tableExist("users")) { + UserDao userDao = jdbi.onDemand(UserDao.class); + userDao.createUsersTable(); + userDao.insert(new User("Admin", "AdminPassword")); + } +} +``` + +## No comment + +```java +public void init() { + Jdbi jdbi = getDatabaseConnection(); + initializeUsersTableWithDefaultAdmin(jdbi); +} +``` + +## No comment + +```java +public void init() { + Properties properties = new Properties(); + properties.setProperty("foreign_keys", "true"); + Jdbi jdbi = Jdbi.create(basePath + "data.db", properties) + .installPlugin(new SQLitePlugin()) + .installPlugin(new SqlObjectPlugin()) + .registerArgument(new UUIDArgumentFactory()); + if (!tableExist("users")) { + UserDao userDao = jdbi.onDemand(UserDao.class); + userDao.createUsersTable(); + userDao.insert(new User("Admin", "AdminPassword")); + } +} +``` +## Mémoire de travail + +## Mémoire de travail + + + + +## Mémoire de travail + + + +## Mémoire de travail + + + +## Mémoire de travail + +{height=400px} + +. . . + +* 2 informations : 4 groupes de 5 +* et 1 opération : 4 × 5 = 20 + +## Mémoire de travail + +* 7 +/- 2 éléments (instructions et/ou informations) +* L'entrainement permet de s'approprier des motifs + +cf. travaux de Nelson Cowan, Alan Baddeley, Graham Hitch et neurosciences + +. . . + +```python +print([x for x in ["apple", "banana", "cherry", "kiwi", "mango"] if "a" in x]) +``` + +## Indirection Implicite + +```java +@Before +public void setEnvUp() { utilisateurdao.createTable(); } + +@After +public void tearEnvDown() throws Exception { utilisateurdao.dropTable(); } + +... + +@Test +public void testCreateUtilisateur() { + Utilisateur utilisateur = sampleUtilisateur(); + Response response = target("/utilisateurs").request() + .post(Entity.json(utilisateur)); + Utilisateur entityFromDB = utilisateurdao.load(utilisateur.getName()) + assertEquals(entityFromDB, utilisateur); +} +``` + +## Indirection Implicite + +```java +@Test +public void testCreateUtilisateur() { + resetTableUtilisateur(); + Utilisateur utilisateur = sampleUtilisateur(); + Response response = target("/utilisateurs").request() + .post(Entity.json(utilisateur)); + Utilisateur entityFromDB = utilisateurdao.load(utilisateur.getName()) + assertEquals(entityFromDB, utilisateur); +} +``` + +## Lisibilité du code + +* Moins de 8 lignes. +* Le moins d'opération mentale possible pour le lecteur. +* Entrainements. + +# Tests verts + +## Du code testable + +* Comment faire du code testable ? \ + <i class="fa fa-arrow-right" aria-hidden="true"></i> cf. [Tout est testable](../02-mock/) +* Comment écrire de bons tests ? \ + <i class="fa fa-arrow-right" aria-hidden="true"></i> ***Une partie de la réponse aujourd'hui.*** + + +## Testable ? + +```java +public long TicksElapsedFrom(int year) { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime from = LocalDateTime.of(year, 1, 1, 0, 0); + return Duration.between(from, now).getSeconds(); +} +``` +. . . + +**#NonRepeatable** + +. . . + +```java +public long TicksElapsedFrom(int year, LocalDateTime now) { + LocalDateTime from = LocalDateTime.of(year, 1, 1, 0, 0); + return Duration.between(from, now).getSeconds(); +} +``` +## Testable ? + +```java +public class User { + private String name; + + public void setName(String name) { + this.name = name; + } + + public String getDisplayName(String prefix) { + name = prefix + " " + name; + return name; + } +} +``` + +. . . + +**#SideEffect** + +## Testable ? + +```java +public void triche() { + if (this.tricheActivee) { + for (Bateau bateau : this.bateaux) { + System.out.println(bateau); + } + } +} +``` + +. . . + +**#InputOutput** + +. . . + +```java +public String triche() { + String output = ""; + if (this.tricheActivee) { + for (Bateau bateau : this.bateaux) { + output += bateau + "\n"; + } + } + return output; +} +``` + + +## Testable ? + +```java +public String statement() { + double totalAmount = 0; + String result = "Rental Record for " + getName() + "\n"; + for (Rental each : this.rentals) { + double thisAmount = each.determineAmount(); + result += "\t" + each.getMovie().getTitle() + "\t" + thisAmount + "\n"; + totalAmount += thisAmount; + } + result += "Amount owed is " + totalAmount + "\n"; + return result; +} +``` + +. . . + +**#NoSingleResponsability** + +## Testable = Code propres + +* Fonction pure : + * Sa valeur de retour est la même pour les mêmes arguments + * Son évaluation n'a pas d'effets de bord +* Limiter les mutations +* Des fonctions ou objets dédiés à l'I/O +* Une seul responsabilité (cf. SOLID) + +## Bon ou mauvais test ? + +```java +@Test +void testViewIsGettingUpdated() { + model.addSeller(new MarketSeller("John Doe", 500)); + assertTrue(view.getMarketSellers().isEmpty()); + model.attach(view); + model.addSeller(new MarketSeller("John Dodo", 500)); + assertFalse(view.getMarketSellers().isEmpty()); + assertEquals(view.getMarketSellers().size(), 2); + assertEquals(view.getMarketSellers().get(0).getName(), "John Doe"); + assertEquals(view.getMarketSellers().get(1).getName(), "John Dodo"); +} +``` + +## Qualité d'un test + +* Il est rapide (quelques secondes max) +* Je peux le jouer 2 fois de suite il est encore vraie, même à plusieurs jours d'intervalles. +* Il est explicite, les destinataires de ce test peuvent le lire et le comprendre. +* Il ne test qu'une règle métier. +* Il est indépendant des autres tests : + * je peux jouer les tests dans le désordre + * je peux jouer tous les tests en même temps. +* Je peux restructurer mon code sans avoir à le changer + +## Bon ou mauvais test ? + +```java +@Test +public void stock_exchanges_test(){ + Bourse bourse = new Bourse(800.0); + assertEquals(800.0, bourse.getAmount()); +} +``` + +. . . + +**#NonExplicit** + +## Bon ou mauvais test ? + +```java +@Test +void should_compute_interval_between_2_dates_in_good_order() { + LocalDate startDate = LocalDate.of(2015, 3, 2); + LocalDate endDate = LocalDate.of(2015, 3, 3); + Long days = IntervalDomain.between(startDate, endDate); + assertEquals(1, days); +} +``` + +. . . + +**#Good** + +## Bon ou mauvais test ? + +```java +@Test +void should_raise_an_error_when_endDate_is_before_startDate() { + assertThrows(InvalidParameterException.class, () -> { + LocalDate startDate = LocalDate.of(2025, 3, 2); + LocalDate endDate = LocalDate.of(2020, 4, 3); + IntervalDomain.between(startDate, endDate); + }); +} +``` + +. . . + +**#Good** + +## Bon ou mauvais test ? + +```java +@Test +public void testSurpopulate(){ + grille = new Grid(); + grille.setAlive(1,1); + grille.setAlive(1,2); + grille.setAlive(0,1); + grille.setAlive(0,0); + grille.setAlive(2,2); + grille.playTurn(); + assertFalse(grille.getCell(1,1).isAlive()); +} +``` + +. . . + +**#NonExplicit #Franglais** + +## Bon ou mauvais test ? + +```java +@Test +void un_depot_doit_incrementer_le_solde() { + CompteEnBanque compteDeBob = CompteEnBanque.creer("Bob"); + compteDeBob.depot(12.2); + Assertions.assertEquals(12.2, compteDeBob.solde()); +} +``` + +. . . + +**#WarningRefactoring #WarningImmutability** + +. . . + +```java +@Test +void un_depot_doit_incrementer_le_solde() { + GivenAnAccount() + .with(euro(12.5)) + .whenIDeposit(euro(10)) + .thenTheSoldeShouldBe(euro(22.5)); +} +``` + +## Bon ou mauvais tests ? + +```java +@Test +public void marketPlaceTest() { + MarketPlace marketPlace = new MarketPlace(); + Member alice = new Member("Alice", 1000); + Member bob = new Member("Bob", 1500); + marketPlace.addMember(alice); + assertEquals(marketPlace.toString(), "Member [name=Alice, money=1000]\n" ); + marketPlace.addMember(bob); + assertEquals(marketPlace.toString(), + "Member [name=Alice, money=1000]\n" + + "Member [name=Bob, money=1500]\n" ); + marketPlace.remove(alice); + assertEquals(marketPlace.toString(),"Member [name=Bob, money=1500]\n" ); +} +``` +. . . + +**#NonExplicit #Multiples #TropLong #PascalCase** + +## Bon ou mauvais tests ? + +```java +@Test +public void ajouterUnProduitTest() { + myBourse.ajouterProduit(p1); + assertTrue(myBourse.produitDisponible(p1)); + assertFalse(myBourse.produitDisponible(p2)); +} + +@Test +public void ajouterProduitsTest() { + myBourse.ajouterProduits(p2, p3); + assertTrue(myBourse.produitDisponible(p1)); + assertTrue(myBourse.produitDisponible(p2)); + assertTrue(myBourse.produitDisponible(p3)); +} +``` +. . . + +**#WarningBeforeEach #TestsDependencies #NonExplicit** + +## Bon ou mauvais tests ? + +```java +@Test +void should_instance_member() { + Member alice = new Member("Alice",1000); + assertTrue(alice != null); +} +``` +. . . + +**#NonExplicit #ToujoursVrai** + +## Bon ou mauvais tests ? + +```java +@Test +void testConstructor(){ + assertEquals(10, this.jdv.getTerrain().length); + assertEquals(10, this.jdv.getTerrain()[0].length); +} +``` +. . . + +**#InternalLeak #NotDemeter** + +## Bon ou mauvais tests ? + +```typescript +describe("Game", () => { + it("should add food when no stock", () => { + const game = new Game(100, 100) + game.players = [] + game.foods = [] + game.addFood(new Food()) + expect(game.foods.length).toEqual(1) + }) +}) +``` +. . . + +**#WarningRefactoring #WarningImmutability #NoSingleResponsability** + +## Bon ou mauvais test ? + +```typescript +describe("Game", () => { + it("should add food when no stock", () => { + expect(addFood([]).length).toEqual(1) + }) + it("should add food when no stock", () => { + expect(FoodStock.of([]).add().length).toEqual(1) + }) +}) +``` +. . . + +**#Good** + + +## Bon ou mauvais test ? + +```java +public BookingResponse book() { + int place = new Random().nextInt(0,seats.length); + return new BookingResponse(BookingResponseStatus.OK,seats[place].getNumber()); +} +``` + +. . . + +**#NonRepeatable #NoSingleResponsability** + +## Bon ou mauvais test ? + +```java +@Test +public void shouldReturnTrue() { + SecretSanta s = new SecretSanta(); + assertTrue(s.addSanta("toto")); +} +``` + +. . . + +**#NonExplicit #PascalCase #MissingException** + +## Bon ou mauvais test ? + +:::: {.columns} +::: {.column width="50%"} +```java +public class TirageTest { + Tirage tirage; + String p1; + String p2; + + @BeforeEach + public void initialize() + { + tirage = new Tirage(); + p1 = "Théo"; + p2 = "Nathan"; + } + + @Test + public void test_add() + { + assertTrue(tirage.add(p1)); + assertTrue(tirage.add(p2)); +``` +::: +::: {.column width="50%"} +```java + assertFalse(tirage.add(p1)); + } + + @Test + public void test_affectation() + { + tirage.add(p1); + tirage.add(p2); + tirage.affectation(); + assertTrue( + tirage.affectation().size() + == + tirage.size()); + } +} +``` +::: +:::: + +. . . + +**#AssertEquals #TestsDependencies** + +## Bon ou mauvais test ? + +```java +public class PersonneTest { + Personne p1; + Personne p2; + Personne p3; + + @BeforeEach + public void setUp() { + p1 = new Personne("Robert" ); + p2 = new Personne("Jean"); + p3 = new Personne("Pierre"); + } + + @Test + public void test_set_gifter(){ + p1.setGifter(p2); + assertTrue(p1.getGifter().equals(p2)); + } +} +``` + +. . . + +**#TooComplicated** + +## Bon ou mauvais test ? + +```java +@Test +public void testBookProperty() { + Train t = new Train(); + BookingResponse expected = t.getSeats()[4].getBr(); + assertEquals(expected, t.book(SeatProperty.Duo)); +} +``` + +. . . + +**#NonExplicite** + +## Bon ou mauvais test ? + +```java +void should_return_a_seat_with_seat_property(){ + Train train = new Train(); + assertEquals(26, train.book(Duo).getSeat()); + assertEquals(17, train.book(Square).getSeat()); +} +``` + +. . . + +**#TooTechnical** + +## Bon ou mauvais test ? + +```java +Train t = new Train(); + +@Test +void bookshould_return_place() { + BookingResponse response = new BookingResponse(BookingResponseStatus.OK, 17); + assertEquals(response,t.book()); +} + +@Test +void retourne_une_place_en_respectant_propriete() { + BookingResponse response = new BookingResponse(BookingResponseStatus.OK, 26); + assertEquals(response, t.book(SeatProperty.Duo)); +} + +@Test +void retourne_place_au_plus_proche() { + assertEquals(Seat.of(17, Square, Window), t.book(14)); +} +``` + +. . . + +**#NotExplicit #TestsDependencies** + +## Bon ou mauvais test ? + +```gherkin +Scenario: On retourne une place respectant la propriété en paramètre + Given un train + When on réserve une place avec une propriété SeatProperty.DUO + Then retourne une place avec la propriété SeatProperty.DUO +``` + +. . . + +**#WarningRefactoring #TechnicalLeak** + + +# Anatomie d'un test + +## Les étapes d'un test + +* *Given* : un ensemble d'éléments observables permettant de décrire avec précision l'état de départ. +* *When* : **une** action +* *Then* : L'ensemble des éléments observables du système après l'action permettant de décrire avec précision l'état d'arrivée. + +## 3 étapes + + + +## Given + +:::: {.columns} +::: {.column width="50%"} +Étant donné un échiquier avec : + +* le roi blanc en A5 +* le roi noir en A7 +* un pion blanc en B7 +* une tour blanche en C7 +* une tour noir en A8 + +Et c'est au blanc de jouer. +::: +::: {.column width="50%"} + +::: +:::: + +## When + +Quand le joueur blanc déplace son pion de B7 à B8 +et demande un cavalier noir. + +## Then + +:::: {.columns} +::: {.column width="50%"} +Alors Les blancs gagnent ! + +Échec et mat ! + +::: +::: {.column width="50%"} + +::: +:::: + +## Junit + +```java +@Test +void should_promote_a_pawn_in_an_other_color () { + Chessboard chessboard = new Chessboard() + .with(King.of(White), "A5") + .with(King.of(Black), "A7") + .with(Pawn.of(White), "B7") + .with(Tower.of(White), "C7") + .with(Tower.of(Black), "A8") + chessboard.move("B7", "B8").request(Knight.of(Black)) + assertEquals(chessboard.get("B8"), Knight.of(Black)) +} +``` + +. . . + +```java +@Test +void should_promote_a_pawn_in_an_other_color () { + Chessboard chessboard = new Chessboard().with(Pawn.of(White), "B7") + chessboard.move("B7", "B8").request(Knight.of(Black)) + assertEquals(chessboard.get("B8"), Knight.of(Black)) +} +``` + +## DSL + +```java +@Test +void should_promote_a_pawn_in_an_other_color () { + GivenAChessboard().with(Pawn.of(White), "B7") + .whenMoveFrom("B7").to("B8").andRequestA(Black(Knight())) + .thenAt("B8").shouldFoundA(Black(Knight)) +} +``` + +## Gherkin + +```gherkin +Scenario: Promote a pawn in an other color + Given a chessboard with a "white pawn" in "B7" + When "white" player move from "B7" to "B8" + And request a "black knight" + Then in "B8" we can see a "black knight" +``` + +# Types vs Tests + +## Tester le métier +```python +class Wallet: + def value(self, currency: Currency, rate: RateProvider) -> Amount: + ... +``` +```java +public class Wallet { + public Amount value(Currency currency, RateProvider rate) { + ... +``` +```typescript +export class Wallet { + public value(currency: Currency, rate: RateProvider): Amount { + ... +``` + +## Tester aussi les paramètres +```python +class Wallet: + def value(self, currency, rate): + ... +``` +```python +def test_should_throw_bad_parameter_when_not_currency(): + ... + +def test_should_throw_bad_parameter_when_not_rateProvider(): + ... +``` + +# Qualités d'un test + +* Il est rapide (quelques secondes max) +* Je peux le jouer 2 fois de suite il est encore vraie, même à plusieurs jours d'intervalles. +* Il est explicite, les destinataires de ce test peuvent le lire et le comprendre. +* Il ne test qu'une règle. +* Il est indépendant des autres tests : + * je peux jouer les tests dans le désordre + * je peux jouer tous les tests en même temps. +* Je peux restructurer mon code sans avoir à le changer + +## Qualité d'un exemple + +Pour les tests qui doivent être lut par les biz, les exemples doivent être : + +* ***précis*** : pour éviter les ambigüités, un bon exemple doit clairement définir l'état du système avant et après l'action. Le test doit être facile à reproduire. +* ***réaliste*** : utiliser de vraies exemples issue de la vraie vie qui permétrons aux lecteurs de mieux comprendre le contexte. `Ocean toto = ...` vs `Ocean atlantic = ...` +* ***simple*** à comprendre : évitez la tentation d'explorer toutes la combinatoire. Évitez d'utiliser des tableaux avec des dizaines de lignes et colonnes. Concentrez vous sur des cas précis et représentatifs. + +## Non fonctionnel + +On appel besoins non fonctionnels, les besoins implicites des utilisateurs évidents et les attentes des utilisateurs non évidents comme : + +* Le service doit répondre en moins de 200ms +* Le programme doit être paramétrable par un unique fichier JSON + +Les tests doivent aussi couvrir ces attentes. + + + + + + + diff --git a/README.md b/README.md index 33d033c..b59a32a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # R4.02 - Qualité de développement -[Version en ligne](https://univlille.gitlab.io/iut-info/r4.02/cours/) +[Version en ligne](http://univlille.gitlab.io/iut-info/r4.02/cours/) ## Fiche ressource @@ -31,7 +31,8 @@ Tests, tests de non régression, tests d'intégration ## Déroulé du cours * [Retour sur le TDD](./01-tdd/) -* [Tester le non testable](./02-mock/) +* [Tout est testable](./02-mock/) +* [Il est beau mon test](./03-tests/) ## TPs diff --git a/sample-code/pom.xml b/sample-code/pom.xml new file mode 100644 index 0000000..35f6bd3 --- /dev/null +++ b/sample-code/pom.xml @@ -0,0 +1,60 @@ +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <properties> + <junit.version>5.7.0</junit.version> + </properties> + + <groupId>fr.univlille.iut.info.r402</groupId> + <artifactId>sample-code</artifactId> + <version>1.0</version> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>17</source> + <target>17</target> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>3.0.0-M5</version> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>0.8.4</version> + <executions> + <execution> + <id>prepare-agent</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + <dependencies> + <!-- junit 5 --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> + diff --git a/sample-code/src/main/java/fr/univlille/iut/info/r402/Cell.java b/sample-code/src/main/java/fr/univlille/iut/info/r402/Cell.java new file mode 100644 index 0000000..67e7b84 --- /dev/null +++ b/sample-code/src/main/java/fr/univlille/iut/info/r402/Cell.java @@ -0,0 +1,4 @@ +package fr.univlille.iut.info.r402; + +public record Cell(CellContent cellContent) { +} diff --git a/sample-code/src/main/java/fr/univlille/iut/info/r402/CellContent.java b/sample-code/src/main/java/fr/univlille/iut/info/r402/CellContent.java new file mode 100644 index 0000000..4f91077 --- /dev/null +++ b/sample-code/src/main/java/fr/univlille/iut/info/r402/CellContent.java @@ -0,0 +1,5 @@ +package fr.univlille.iut.info.r402; + +public enum CellContent { + Water, Fish, Shark; +} diff --git a/sample-code/src/main/java/fr/univlille/iut/info/r402/Ocean.java b/sample-code/src/main/java/fr/univlille/iut/info/r402/Ocean.java new file mode 100644 index 0000000..5d53f2a --- /dev/null +++ b/sample-code/src/main/java/fr/univlille/iut/info/r402/Ocean.java @@ -0,0 +1,14 @@ +package fr.univlille.iut.info.r402; + +public class Ocean { + private final Cell[][] internalRepresentation; + + public Ocean(int height, int width) { + this.internalRepresentation = new Cell[height][width]; + } + + public Cell[][] getInternalRepresentation() { + return internalRepresentation; + } + +} diff --git a/sample-code/src/test/java/OceanTest.java b/sample-code/src/test/java/OceanTest.java new file mode 100644 index 0000000..509dd98 --- /dev/null +++ b/sample-code/src/test/java/OceanTest.java @@ -0,0 +1,16 @@ +import fr.univlille.iut.info.r402.Cell; +import fr.univlille.iut.info.r402.CellContent; +import fr.univlille.iut.info.r402.Ocean; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static fr.univlille.iut.info.r402.CellContent.Fish; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class OceanTest { + @Test + void fish_should_move() { + new Ocean(3,3).getInternalRepresentation()[0][0] = new Cell(Fish); + assertEquals(Fish, new Ocean(3,3).getInternalRepresentation()[0][0].cellContent()); + } +} -- GitLab