|
|||||||
|
|
|
![]() |
|
|
Strumenti |
|
|
#1 |
|
Senior Member
Iscritto dal: Oct 2002
Città: San Jose, California
Messaggi: 11794
|
Test Driven Development: un esempio pratico (CICLO 1, Task 2.5)
Io e Vicius faremo Pair Programming in questo topic, svolgendo il task 2.5 totalmente test driven.
Non abbiamo ne' scritto il codice, ne' preparato i test prima di scrivere questo topic; avverra' tutto "live", e lo impostiamo come un dialogo fra me e lui, proprio come se fossimo davanti allo stesso PC. Vediamo che cosa ne esce Questo e' il task: Riprodurre un suono quando il diamante collide con un bordo Parto io, Vicius, scrivo il primo test nel file TestGemCollisionSound.java. Ci serve un qualche modo per comunicare alla gemma che vogliamo riprodurre un suono e il suono da riprodurre. Codice:
package it.diamonds.tests;
import it.diamonds.Gem;
import it.diamonds.audio.Sound;
import it.diamonds.audio.SoundException;
import junit.framework.TestCase;
public class TestGemCollisionSound extends TestCase
{
public void testSetCollisionSound()
{
Sound sound = null;
try
{
sound = Sound.createForTesting("diamond");
}
catch(SoundException e)
{
fail();
}
Gem gem = Gem.createGemForTesting();
gem.setCollisionSound(sound);
assertTrue("Collision sound not set", gem.isCollisionSoundSet());
}
}
Cercheremo di seguire TDD alla lettera, di volta in volta il minimo cambiamento possibile per far passare il test e poi eliminazione di tutte le duplicazioni nel codice.
__________________
"We in the game industry are lucky enough to be able to create our visions" @ NVIDIA Ultima modifica di fek : 03-10-2005 alle 22:02. |
|
|
|
|
|
#2 |
|
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
eccomi.
Allora prima di tutto ho creato delle stubs per far compilare il programma. Questo è il codice: Codice:
public void setCollisionSound(Sound collisionSound)
{
return;
}
public boolean isCollisionSoundSet()
{
return true;
}
Prima di tutto ho aggiunto una variabile dove meorizzare il "suono". Codice:
private Sound collisionSound; Codice:
public void setCollisionSound(Sound collisionSound)
{
this.collisionSound = collisionSound;
}
Codice:
public boolean isCollisionSoundSet()
{
return collisionSound != null;
}
ciao |
|
|
|
|
|
#3 |
|
Senior Member
Iscritto dal: Oct 2002
Città: San Jose, California
Messaggi: 11794
|
Vicius e' bravo, quindi lui puo' permettersi di saltare qualche passaggio, ma se avessimo voluto fare un passettino per volta, per andare piano piano, avremmo prima dovuto scrivere gli stub di modo da far fallire il test.
Quindi: Codice:
public void setCollisionSound(Sound collisionSound)
{
return;
}
public boolean isCollisionSoundSet()
{
return false;
}
Eccolo: Codice:
public void setCollisionSound(Sound collisionSound)
{
return;
}
public boolean isCollisionSoundSet()
{
return true;
}
Ora prima di scrivere altro codice, ci serve un altro test che fallisce, eccolo: Codice:
public void testCollisionSoundNotSet()
{
Gem gem = Gem.createGemForTesting();
assertFalse("Collision sound not set", gem.isCollisionSoundSet());
}
Quindi? Ecco un'implementazione semplice che fa passare entrambi i test: Codice:
private boolean collisionSoundSet = false;
public void setCollisionSound(Sound collisionSound)
{
collisionSoundSet = true;
}
public boolean isCollisionSoundSet()
{
return collisionSoundSet;
}
Vicius, scrivi un test che ci porti da qui alla tua implementazione ora. A te, io lo implemento.
__________________
"We in the game industry are lucky enough to be able to create our visions" @ NVIDIA |
|
|
|
|
|
#4 |
|
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
Ecco il test.
Codice:
public void testGetCollisionSound()
{
Sound sound = null;
Audio audio = new Audio();
audio.initListener();
try
{
sound = Sound.createForTesting("diamond");
}
catch(SoundException e)
{
fail();
}
Gem gem = Gem.createGemForTesting();
gem.setCollisionSound(sound);
assertEquals("i due suoni non sono uguali", gem.getCollisionSound(), sound);
}
|
|
|
|
|
|
#5 |
|
Senior Member
Iscritto dal: Oct 2002
Città: San Jose, California
Messaggi: 11794
|
Mi serve per prima cosa far compilare il test:
Codice:
public Sound getCollisionSound()
{
return null;
}
Mi serve l'implementazione piu' semplice possibile per farlo passare. Visto che setCollisionSound() mi passa un oggetto e poi il test vuole che getCollisionSound() gli restituisca lo stesso oggetto, questa e' l'implementazione piu' semplice che mi viene in mente: Codice:
private boolean collisionSoundSet = false;
private Sound collisionSound;
public void setCollisionSound(Sound sound)
{
collisionSoundSet = true;
collisionSound = sound;
}
public boolean isCollisionSoundSet()
{
return collisionSoundSet;
}
public Sound getCollisionSound()
{
return collisionSound;
}
Ma c'e' una duplicazione che mi annoia. collisionSoundSet e collisionSound stanno esprimendo la stessa informazione in due posti diversi. C'e' una duplicazione e la elimino subito cosi'. Codice:
private Sound collisionSound;
public void setCollisionSound(Sound sound)
{
collisionSound = sound;
}
public boolean isCollisionSoundSet()
{
return collisionSound != null;
}
public Sound getCollisionSouind()
{
return collisionSound;
}
Siamo arrivati passettino per passettino alla stessa prima soluzione di Vicius, pero' in piu' abbiamo altri due test che saranno sempre li' a dirci se combiniamo qualche casino. Vicius ho dubbio, che succede se passo un suono nullo a setCollisionSound()? Aspetta, te lo scrivo in un test: Codice:
public void testNullCollisionSound()
{
Gem gem = Gem.createGemForTesting();
gem.setCollisionSound(null);
assertFalse("Collision sound still set", gem.isCollisionSoundSet());
}
__________________
"We in the game industry are lucky enough to be able to create our visions" @ NVIDIA Ultima modifica di fek : 03-10-2005 alle 22:42. |
|
|
|
|
|
#6 | |
|
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
Quote:
Codice:
public void testNullCollisionSound()
{
Gem gem = Gem.createGemForTesting();
try {
gem.setCollisionSound(null);
} catch (ArgumentNullException e) {
fail("boom!");
}
}
|
|
|
|
|
|
|
#7 |
|
Senior Member
Iscritto dal: Oct 2002
Città: San Jose, California
Messaggi: 11794
|
Perfetto, a parte quell'orribile indentazione
Abbiamo preso una decisione di design su come trattare il valore null, ma la cosa interessante e' che ci siamo comunicati il design via test, e la decisione stessa via test. In maniera chiara. Semplifico un po' i test e andiamo avanti, ho visto qualche duplicazione e codice inutile da eliminare. Ecco i test: Codice:
public class TestGemCollisionSound extends TestCase
{
public void testCollisionSoundNotSet()
{
Gem gem = Gem.createGemForTesting();
assertFalse("Collision sound not set", gem.isCollisionSoundSet());
}
public void testSetCollisionSound() throws SoundException
{
Sound sound = Sound.createForTesting("diamond");
Gem gem = Gem.createGemForTesting();
gem.setCollisionSound(sound);
assertTrue("Collision sound not set", gem.isCollisionSoundSet());
}
public void testNullCollisionSound()
{
Gem gem = Gem.createGemForTesting();
try
{
gem.setCollisionSound(null);
}
catch (Exception e)
{
fail("boom!");
}
}
public void testGetCollisionSound() throws SoundException
{
Sound sound = Sound.createForTesting("diamond");
Gem gem = Gem.createGemForTesting();
gem.setCollisionSound(sound);
assertEquals("collision sound is wrong", gem.getCollisionSound(), sound);
}
}
Codice:
public class TestGemCollisionSound extends TestCase
{
private Sound sound;
private Gem gem;
public void setUp() throws SoundException
{
sound = Sound.createForTesting("diamond");
gem = Gem.createGemForTesting();
}
public void testCollisionSoundNotSet()
{
assertFalse("Collision sound not set", gem.isCollisionSoundSet());
}
public void testSetCollisionSound() throws SoundException
{
gem.setCollisionSound(sound);
assertTrue("Collision sound not set", gem.isCollisionSoundSet());
}
public void testNullCollisionSound()
{
try
{
gem.setCollisionSound(null);
}
catch (Exception e)
{
fail("boom!");
}
}
public void testGetCollisionSound() throws SoundException
{
gem.setCollisionSound(sound);
assertEquals("collision sound is wrong", gem.getCollisionSound(), sound);
}
}
Prossimo test ora, Vicius, che suggerisci?
__________________
"We in the game industry are lucky enough to be able to create our visions" @ NVIDIA |
|
|
|
|
|
#8 |
|
Senior Member
Iscritto dal: Oct 2002
Città: San Jose, California
Messaggi: 11794
|
Non fosse che ho sbagliato un test.
Codice:
public void testNullCollisionSound()
{
try
{
gem.setCollisionSound(null);
}
catch (Exception e)
{
fail("boom!");
}
}
Ecco il test giusto: Codice:
public void testNullCollisionSound()
{
try
{
gem.setCollisionSound(null);
}
catch (Exception e)
{
return;
}
fail("Exception not thrown");
}
Vicius, a te. Il codice piu' semplice che fa passare il test.
__________________
"We in the game industry are lucky enough to be able to create our visions" @ NVIDIA Ultima modifica di fek : 03-10-2005 alle 23:13. |
|
|
|
|
|
#9 |
|
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
Come prossimo test possiamo a controlalre che il suonon non venga riprodotto prima della collisione. Questo è il codice del test:
Codice:
public void testSoundBeforeCollision()
{
Sound sound = null;
try
{
sound = Sound.createForTesting("diamond");
}
catch(SoundException e)
{
fail();
}
Gem gem = Gem.createGemForTesting();
gem.setCollisionSound(sound);
assertFalse("Sound must not be played before a collision",
sound.wasPlayed());
}
|
|
|
|
|
|
#10 |
|
Senior Member
Iscritto dal: Oct 2002
Città: San Jose, California
Messaggi: 11794
|
Vai troppo veloce per me!
Fai prima passare il mio test.
__________________
"We in the game industry are lucky enough to be able to create our visions" @ NVIDIA |
|
|
|
|
|
#11 | |
|
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
Quote:
Allora in setCollisionSound non ho dovuto far altro che inseire un if per lanciare la nuova eccezione. abbiamo quindi questo codice. Codice:
public void setCollisionSound(Sound sound)
throws NullPointerException
{
if (sound == null)
{
throw new NullPointerException();
}
collisionSound = sound;
}
ciao |
|
|
|
|
|
|
#12 |
|
Senior Member
Iscritto dal: Oct 2002
Città: San Jose, California
Messaggi: 11794
|
Ora il tuo test, ecco la versione semplificata:
Codice:
public void testSoundBeforeCollision()
{
gem.setCollisionSound(sound);
assertFalse("Sound must not be played before a collision",
sound.wasPlayed());
}
Una regola del TDD e' che non si aggiunge nessuna funzionalita' se non si ha un test che fallisce. Quindi ora ci serve un test che fallisca per proseguire: Eccolo qui: Codice:
public void testSoundAfterCollision()
{
gem.setCollisionSound(sound);
Input input = Input.createInputForTesting();
input.generateKey(KeyCode.vk_Left, true);
gem.setSpeed(1000.0f, 1000.0f);
gem.reactToInput(input);
assertTrue("Sound must be played after a collision",
sound.wasPlayed());
}
Mettiamo da parte questo test e ne scriviamo uno per setSpeed prima, che dici? Facciamo passare quello e poi torniamo qui. Ecco il test: Codice:
public void testSetSpeed() throws TextureNotFoundException
{
Input input = Input.createInputForTesting();
input.generateKey(KeyCode.vk_Left, true);
gem().setSpeed(10.0, 0.0);
gem().reactToInput(input);
assertEquals(90.0f, gem().getX());
}
A te.
__________________
"We in the game industry are lucky enough to be able to create our visions" @ NVIDIA |
|
|
|
|
|
#13 |
|
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
Perfetto procediamo con questo setSpeed. Questa volta cerco di procedere piu lentamente. Dunque prima lo stub.
Codice:
public void setSpeed(float vx, float vy)
{
return;
}
Ok. Facciamo passare questo benedetto test. Dunque visto che Gem è una classe figlia di Sprite che non devo far altro che chiamare setSpeed di sprite dal mio codice in questo modo. Codice:
public void setSpeed(float vx, float vy)
{
super.setSpeed(vx, vy);
}
ciao |
|
|
|
|
|
#14 | |
|
Senior Member
Iscritto dal: Oct 2002
Città: San Jose, California
Messaggi: 11794
|
Quote:
Direi di togliere il metodo da Sprite e spostarlo a livello di Gem. Abbiamo un bel test che fallisce, dovrebbe essere piuttosto immediato farlo passare. Possiamo andare passo passo.
__________________
"We in the game industry are lucky enough to be able to create our visions" @ NVIDIA |
|
|
|
|
|
|
#15 |
|
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
Come mi hai fatto notare il baco è banale. I valori vx e vy non vengono usati come velocita ma come variabili temporane mentre la velocita è sempre 1f. Per correggere è bastato cambiare nome alle due variabili temporane ed usare le due variabili vy e vx al posto del valore fisso di velocità.
qindi il codice ora è: Codice:
public void reactToInput(Input input)
{
float dx = 0f;
float dy = 0f;
if(input.isKeyUp())
{
dy += vy;
}
if(input.isKeyDown())
{
dy -= vy;
}
if(input.isKeyLeft())
{
dx -= vx;
}
if(input.isKeyRight())
{
dx += vx;
}
move(dx, dy);
}
ciao |
|
|
|
|
|
#16 |
|
Senior Member
Iscritto dal: Oct 2002
Città: San Jose, California
Messaggi: 11794
|
Ora possiamo tornare al test di prima che compila... e fallisce.
A te
__________________
"We in the game industry are lucky enough to be able to create our visions" @ NVIDIA |
|
|
|
|
|
#17 |
|
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
Non ci resta che far partire il suono. Quindi vado nel metodo move della classe Gem e aggiungo il seguente codice subito dopo stopPulse();
Codice:
collisionSound.play(); ciao |
|
|
|
|
|
#18 |
|
Senior Member
Iscritto dal: Oct 2002
Città: San Jose, California
Messaggi: 11794
|
Mamma butta la pasta?
Ora, ci sono condizioni particolari da testare secondo te?
__________________
"We in the game industry are lucky enough to be able to create our visions" @ NVIDIA |
|
|
|
|
|
#19 | |
|
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
Quote:
Dunque cosa succede se fate partire ad esempio Game o in uno sprite non impostate il suono di collisione? Semplice il sistema salta perchè mi sono, volutamente ,scordato di testa se collisionSound è diverso da null prima di riprodurlo. Quindi scrivo prima un bel test che faccia scattare il baco.Codice:
public void testPlayNullCollisionSound()
{
Input input = Input.createInputForTesting();
input.generateKey(KeyCode.vk_Left, true);
gem.setSpeed(1000.0f, 1000.0f);
try
{
gem.reactToInput(input);
}
catch (Exception e)
{
fail("collision sound è null");
}
}
Codice:
if (collisionSound != null)
{
collisionSound.play();
}
|
|
|
|
|
|
|
#20 |
|
Senior Member
Iscritto dal: Oct 2002
Città: San Jose, California
Messaggi: 11794
|
Che dici si va a dormire ora o c'e' altro?
__________________
"We in the game industry are lucky enough to be able to create our visions" @ NVIDIA |
|
|
|
|
| Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 05:38.











,scordato di testa se collisionSound è diverso da null prima di riprodurlo. Quindi scrivo prima un bel test che faccia scattare il baco.








