<div class="page"> <div class="cover text-center"> <img class="mx-auto" src=/itb/images/logo_mislata.png alt="logo"> # TDD - Fizz Buzz <div class="text-end fit-content ms-auto my-3 mt-auto pt-3"> <p><strong>Autor:</strong> Joan Puigcerver Ibáñez</p> <p><strong>Correu electrònic:</strong> j.puigcerveribanez@edu.gva.es</p> <p><strong>Curs:</strong> 2024/2025</p> </div> <div> <p class="fw-bold mb-0">Llicència: BY-NC-SA</p> <p class="d-none d-md-block">(Reconeixement - No Comercial - Compartir Igual)</p> <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.ca" target="_blank"> <img class="mx-auto" src="/itb/images/license.png" alt="Licence"/> </a> </div><!--license--> </div><!--cover--> </div><!--page--> {:toc} ## Objectiu Veure el procés de desenvolupament d'un problema utilitzant TDD. ## Fizz Buzz Ens han demanat implementar un programa que, donat un nombre, ens retorne els següents valors segons les condicions: - Si el nombre és divisible per 3, retornar "Fizz". - Si el nombre és divisible per 5, retornar "Buzz". - Si el nombre és divisible per 3 i per 5, retornar "FizzBuzz". - En qualsevol altre cas, retornar el nombre. ### Exemples ```text 1 -> 1 2 -> 2 3 -> Fizz 4 -> 4 5 -> Buzz 6 -> Fizz 7 -> 7 8 -> 8 9 -> Fizz 10 -> Buzz 11 -> 11 12 -> Fizz 13 -> 13 14 -> 14 15 -> FizzBuzz 16 -> 16 ``` ## Implementació La implementació es realitzarà en classe `FizzBuzz` en el mètode `transform()` mitjançant Desenvolupament Guiat per Tests (TDD). Per tant, el primer pas és crear la classe amb les proves `FizzBuzzTest` i començar a realitzar iteracions: - RED: Crear una prova que falle. - GREEN: Implementar el codi mínim perquè la prova passe. - REFACTOR: Refactoritzar el codi. ### Iteració 1 ##### RED Creem la primera prova, en la qual comprovem que el mètode `transform(1)` ens retorna `1`. ```java package ud5.examples.fizzbuzz; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; class FizzBuzzTest { @Test @DisplayName("FizzBuzz class should exist") void fizzBuzzClassShouldExist() { FizzBuzz fizzBuzz = new FizzBuzz(); } } ``` Aquesta prova fallarà inicialment (RED), ja que la classe `FizzBuzz` no existeix. ##### GREEN El primer pas per aconseguir que la prova passe (GREEN) és crear la classe `FizzBuzz`. ```java package ud5.examples; public class FizzBuzz { } ``` Si executem la prova anterior, veurem que ja passa en verd (GREEN). ##### REFACTOR En aquest pas hauriem de refactoritzar el codi per millorar-lo, però de moment no hi ha res a refactoritzar. ### Iteració 2 ##### RED Creem la segona prova, en la qual comprovem que existeix el mètode `transform()`. ```java @Test @DisplayName("FizzBuzz::transform() method should exist") void fizzBuzzTransformMethodShouldExist() { FizzBuzz fizzBuzz = new FizzBuzz(); fizzBuzz.transform(); } ``` Aquesta prova fallarà inicialment (RED), ja que el mètode `transform()` no existeix. ##### GREEN El primer pas per aconseguir que la prova passe (GREEN) és crear el mètode `transform()`. ```java public class FizzBuzz { public void transform() { } } ``` ##### REFACTOR Encara no hi ha res a refactoritzar. ### Iteració 3 ##### RED Creem la tercera prova, en la qual comprovem que el mètode `transform()` ha d'acceptar un nombre enter com a paràmetre. ```java @Test @DisplayName("FizzBuzz::transform() method should have an int parameter") void fizzBuzzTransformMethodShouldHaveIntParameter() { FizzBuzz fizzBuzz = new FizzBuzz(); fizzBuzz.transform(1); } ``` ##### GREEN El primer pas per aconseguir que la prova passe (GREEN) és afegir el paràmetre `int number` al mètode `transform()`. ```java public class FizzBuzz { public void transform(int n) { } } ``` En aquest punt, si executem les proves, veurem que la prova passa en verd (GREEN), però l'anterior prova falla perquè el mètode `transform()` no pot ser invocat sense paràmetres, per tant, hem de decidir com volem procedir. En aquest cas, com que la prova que falla és més antiga i prova un comportament que també estem comprovant amb la prova actual, decidim eliminar-la. ::: info S'ha comentat en compte d'eliminar-la perquè quede registrat el procés seguit. ::: ```java public class FizzBuzzTest { @Test @DisplayName("FizzBuzz class should exist") void classFizzBuzzShouldExist() { FizzBuzz fizzBuzz = new FizzBuzz(); } /* @Test @DisplayName("FizzBuzz::transform() method should exist") void fizzBuzzTransformMethodShouldExist() { FizzBuzz fizzBuzz = new FizzBuzz(); fizzBuzz.transform(); } */ @Test @DisplayName("FizzBuzz::transform() method should have an int parameter") void fizzBuzzTransformMethodShouldHaveIntParameter() { FizzBuzz fizzBuzz = new FizzBuzz(); fizzBuzz.transform(1); } } ``` Si executem les proves en aquest punt, veurem que totes passen en verd (GREEN). ##### REFACTOR A banda de l'eliminiació de la prova, no hi ha res a refactoritzar. ### Iteració 4 ##### RED Creem la quarta prova, que provem el primer cas: el mètode `transform(1)` ens retorna `1`. ```java @Test @DisplayName("FizzBuzz::transform(1) should return 1") void fizzBuzzTransform_givenValue1_shouldReturn1() { FizzBuzz fizzBuzz = new FizzBuzz(); int result = fizzBuzz.transform(1); assertEquals(1, result); } ``` ##### GREEN El primer pas per aconseguir que la prova passe (GREEN) és modificar el valor de retorn de la funció i retornar el valor `1` al mètode `transform()`. ```java public class FizzBuzz { public int transform(int n) { return 1; } } ``` ##### REFACTOR Encara no hi ha res a refactoritzar. ### Iteració 5 ##### RED Creem la cinquena prova, que provem el segon cas: el mètode `transform(2)` ens retorna `2`. ```java @Test @DisplayName("FizzBuzz::transform(2) should return 2") void fizzBuzzTransform_givenValue2_shouldReturn2() { FizzBuzz fizzBuzz = new FizzBuzz(); int result = fizzBuzz.transform(2); assertEquals(2, result); } ``` ##### GREEN En aquest cas, podem fer que la prova passe (GREEN) retornant el valor `2` al mètode `transform()`. ```java public class FizzBuzz { public int transform(int n) { return 2; } } ``` Aquesta solució passa la prova que acabem de fer, però no passa la prova anterior, ja que si passem el valor `1` al mètode `transform()`, aquest retorna `2` en comptes de `1`, per tant, la solució no és vàlida. Gràcies a haver fet la prova anterior, ens hem adonat que la solució no és vàlida i li hem tret rendiment a la prova escrita. Una solució que sí que funciona per a les dues proves és la següent: ```java public class FizzBuzz { public int transform(int n) { if (n == 2) return 2; return 1; } } ``` ##### REFACTOR Ja passen totes les proves en verd (GREEN), i ara ens podem plantejar si la solució és òptima o no. En aquest cas, podem refactoritzar el codi perquè siga més llegible i funcione en el cas general: - En qualsevol altre cas, retornar el nombre. ```java public class FizzBuzz { public int transform(int n) { return n; } } ``` ### Iteració 6 ##### RED Passem al primer cas especial: Si el nombre és divisible per 3, retornar "Fizz", per tant, creem la prova: ```java @Test @DisplayName("FizzBuzz::transform(3) should return Fizz") void fizzBuzzTransform_givenValue3_shouldReturnFizz() { FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(3); assertEquals("Fizz", result); } ``` ##### GREEN En aquest cas, perquè funcione hem de canviar el tipus de retorn al mètode `transform()` i retornar un `String`. ```java public class FizzBuzz { public String transform(int n) { if (n == 3) return "Fizz"; return String.valueOf(n); } } ``` Aquest canvi fa que les proves anteriors fallen, ja que esperen un `int` i ara reben un `String`,i per tant, hem de refactoritzar les proves anteriors perquè esperen un `String`. ```java @Test @DisplayName("FizzBuzz::transform(1) should return 1") void fizzBuzzTransform_givenValue1_shouldReturn1() { FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(1); assertEquals("1", result); } ``` ##### REFACTOR Hem de refactoritzar les proves anteriors perquè esperen un `String`. ### Iteració 7 En aquesta iteració podríem comprovar el cas 4, que hauria de retornar 4. ```java @Test @DisplayName("FizzBuzz::transform(4) should return 4") void fizzBuzzTransform_givenValue4_shouldReturn4() { FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(4); assertEquals("4", result); } ``` No obstant això, si executem aquest test veurem que directament passa correctament en verd (GREEN) i per tant, la funcionalitat que esperem d'aquest test ja ha segut provada anteriorment i, per tant, no és necessari fer aquesta prova. ##### RED Anem a provar el cas 5, que hauria de retornar "Buzz". ```java @Test @DisplayName("FizzBuzz::transform(5) should return Buzz") void fizzBuzzTransform_givenValue5_shouldReturnBuzz() { FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(5); assertEquals("Buzz", result); } ``` ##### GREEN Per aconseguir que la prova passe (GREEN), hem de modificar el mètode `transform()` perquè retorne "Buzz" quan el paràmetre és 5. ```java public String transform(int n) { if (n == 3) return "Fizz"; if (n == 5) return "Buzz"; return String.valueOf(n); } ``` ##### REFACTOR No hi ha res a refactoritzar. ### Iteració 8 ##### RED Anem a provar el cas 6, que hauria de retornar "Fizz". ```java @Test @DisplayName("FizzBuzz::transform(6) should return Fizz") void fizzBuzzTransform_givenValue6_shouldReturnFizz() { FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(6); assertEquals("Fizz", result); } ``` ##### GREEN Per aconseguir que la prova passe (GREEN), hem de modificar el mètode `transform()` perquè retorne "Fizz" quan el paràmetre és 6. ```java public String transform(int n) { if (n == 3 || n == 6) return "Fizz"; if (n == 5) return "Buzz"; return String.valueOf(n); } ``` ##### REFACTOR En aquest cas, ens adonem que la condició `n == 3 || n == 6` pot ser simplificada i generalitzada a `n % 3 == 0`. ```java public String transform(int n) { if (n % 3 == 0) return "Fizz"; if (n == 5) return "Buzz"; return String.valueOf(n); } ``` ### Iteració 9 Els casos 7, 8 i 9 ja han segut provats anteriorment, per tant, no és necessari fer aquestes proves. ##### RED Anem a provar el cas 10, que hauria de retornar "Buzz". ```java @Test @DisplayName("FizzBuzz::transform(10) should return Buzz") void fizzBuzzTransform_givenValue10_shouldReturnBuzz() { FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(10); assertEquals("Buzz", result); } ``` ##### GREEN Per aconseguir que la prova passe (GREEN), hem de modificar el mètode `transform()` perquè retorne "Buzz" quan el paràmetre és 10. ```java public String transform(int n) { if (n % 3 == 0) return "Fizz"; if (n % 5 == 0) return "Buzz"; return String.valueOf(n); } ``` En aquest cas ja hem fet servir el mòdul (`%`) per a comprovar si un nombre és divisible per un altre. ##### REFACTOR Generalització de la condició de Buzz. ### Iteració 10 Elss casos 11, 12, 13 i 14 ja han segut provats anteriorment, per tant, no és necessari fer aquestes proves. ##### RED Anem a provar el cas 15, que hauria de retornar "FizzBuzz". ```java @Test @DisplayName("FizzBuzz::transform(15) should return FizzBuzz") void fizzBuzzTransform_givenValue15_shouldReturnFizzBuzz() { FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(15); assertEquals("FizzBuzz", result); } ``` ##### GREEN Per aconseguir que la prova passe (GREEN), hem de modificar el mètode `transform()` perquè retorne "FizzBuzz" quan el paràmetre és 15. ```java public String transform(int n) { if (n % 3 == 0) return "Fizz"; if (n % 5 == 0) return "Buzz"; if (n == 15) return "FizzBuzz"; return String.valueOf(n); } ``` Si executem aquesta prova veurem que falla, ja que el mètode `transform()` retorna "Fizz" quan el paràmetre és 15, en compte de "FizzBuzz". Això es deu a que la condició `n % 3 == 0` és certa i per tant, retorna "Fizz" i no continua avaluant les altres condicions. Caldria canviar l'ordre de les condicions perquè la condició `n == 15` es comprove abans que la condició `n % 3 == 0`. ```java public String transform(int n) { if (n == 15) return "FizzBuzz"; if (n % 3 == 0) return "Fizz"; if (n % 5 == 0) return "Buzz"; return String.valueOf(n); } ``` ##### REFACTOR No hi ha res a refactoritzar. ### Iteració 11 ##### RED L'últim cas que ens queda per comprovar és el 30, que hauria de retornar "FizzBuzz". ```java @Test @DisplayName("FizzBuzz::transform(30) should return FizzBuzz") void fizzBuzzTransform_givenValue30_shouldReturnFizzBuzz() { FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(30); assertEquals("FizzBuzz", result); } ``` ##### GREEN Per aconseguir que la prova passe (GREEN), hem de modificar el mètode `transform()` perquè retorne "FizzBuzz" quan el paràmetre és 30. ```java public String transform(int n) { if (n == 15 || n == 30) return "FizzBuzz"; if (n % 3 == 0) return "Fizz"; if (n % 5 == 0) return "Buzz"; return String.valueOf(n); } ``` ##### REFACTOR Podem optimitzar la condició `n == 15 || n == 30` a `n % 15 == 0`. ```java public String transform(int n) { if (n % 15 == 0) return "FizzBuzz"; if (n % 3 == 0) return "Fizz"; if (n % 5 == 0) return "Buzz"; return String.valueOf(n); } ``` ## Resultat final ### Implementació ```java package ud5.examples; public class FizzBuzz { public String transform(int n){ if (n % 15 == 0) return "FizzBuzz"; else if (n % 3 == 0) return "Fizz"; else if (n % 5 == 0) return "Buzz"; return String.valueOf(n); } } ``` ### Proves ```java package ud5.examples; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class FizzBuzzTest { @Test @DisplayName("FizzBuzz class should exist") void fizzBuzzClassShouldExist(){ FizzBuzz fizzBuzz = new FizzBuzz(); } /* @Test @DisplayName("FizzBuzz::transform() method should exist") void fizzBuzzTransformMethodShouldExist(){ FizzBuzz fizzBuzz = new FizzBuzz(); fizzBuzz.transform(); } */ @Test @DisplayName("FizzBuzz::transfrom() should have an int parameter") void fizzBuzzTransformShouldHaveIntParameter(){ FizzBuzz fizzBuzz = new FizzBuzz(); fizzBuzz.transform(1); } @Test @DisplayName("FizzBuzz::transform(1) should return 1") void fizzBuzzTransform_given1_shouldReturn1(){ FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(1); assertEquals("1", result); } @Test @DisplayName("FizzBuzz::transform(2) should return 2") void fizzBuzzTransform_given2_shouldReturn2(){ FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(2); assertEquals("2", result); } @Test @DisplayName("FizzBuzz::transform(3) should return Fizz") void fizzBuzzTransform_given3_shouldReturnFizz(){ FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(3); assertEquals("Fizz", result); } @Test @DisplayName("FizzBuzz::transform(5) should return Buzz") void fizzBuzzTransform_given5_shouldReturnBuzz(){ FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(5); assertEquals("Buzz", result); } @Test @DisplayName("FizzBuzz::transform(6) should return Fizz") void fizzBuzzTransform_given6_shouldReturnFizz(){ FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(6); assertEquals("Fizz", result); } @Test @DisplayName("FizzBuzz::transform(10) should return Buzz") void fizzBuzzTransform_given10_shouldReturnBuzz(){ FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(10); assertEquals("Buzz", result); } @Test @DisplayName("FizzBuzz::transform(15) should return FizzBuzz") void fizzBuzzTransform_given15_shouldReturnFizzBuzz(){ FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(15); assertEquals("FizzBuzz", result); } @Test @DisplayName("FizzBuzz::transform(30) should return FizzBuzz") void fizzBuzzTransform_given30_shouldReturnFizzBuzz(){ FizzBuzz fizzBuzz = new FizzBuzz(); String result = fizzBuzz.transform(30); assertEquals("FizzBuzz", result); } } ``` ## Conclusions - Hem vist el procés de desenvolupament d'un problema utilitzant TDD. - Hem comprovat la utilitat de les proves per compronvar el funcionament del nostre codi. - Hem detectat errors que potser no haguérem detectat sense les proves. - Les proves han guiats el nostre desenvolupament, que ens han aportat una utilitat immediata i ens han permés refactoritzar el codi. - Si s'afegeix una nova regla, podem afegir noves proves i modificar el codi amb la seguretat que la funcionalitat anterior seguirà sent correcta. - La classe `FizzBuzzTest` ens proporciona una documentació viva del comportament de la classe `FizzBuzz`.