diff --git a/03-tests.md b/03-tests.md
new file mode 100644
index 0000000000000000000000000000000000000000..ce31b557954ebd3b00445f6fccd7dbea6b339d4e
--- /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 33d033c1611ba65b766d9ca10e61482acf3be3ab..b59a32ac8531293d2aa255c8af12b8a0a58b3896 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 0000000000000000000000000000000000000000..35f6bd391bb541904be4bdac1394323cb854661f
--- /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 0000000000000000000000000000000000000000..67e7b840e1ca7794c60de8e4a0c1b6367fc5d3bf
--- /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 0000000000000000000000000000000000000000..4f910775cc300d61d11b1111afa4f1f1a4f07cb6
--- /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 0000000000000000000000000000000000000000..5d53f2a634a8215e982c789d757a896695439e3c
--- /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 0000000000000000000000000000000000000000..509dd9874cce4b8aa311120a839420f25ad6a2db
--- /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());
+ }
+}