<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`.