2. Questo non riguarda la
tecnologia
Ma se non c’e’ tecnologia poi
mi dicono che il tdd e’ solo per
il dominio
3. Il Gioco
• Si chiama “Boss”, e’ una specie di GTA su
browser
– Il giocatore ha una vista a volo d’uccello sulla città
e sul personaggio
– Quando il giocatore clicca sulla mappa il
personaggio si sposta
– Il giocatore vede gli altri personaggi sulla mappa
– ….
6. Il Primo Test
public class BossBehavior {
@Test
public void shouldAnswerHttpCall() throws IOException {
new Boss(8080);
WebClient client = new WebClient();
Page page = client.getPage("http://localhost:8080");
assertThat(page.getWebResponse().getStatusCode(), is(200));
}
}
7. La Soluzione
public class Boss {
public Boss(int port) throws IOException {
SelectorThread selector = new SelectorThread();
selector.setPort(port);
selector.setAdapter(new AlwaysReturn200Ok());
selector.listen();
}
}
8. Un po’ di refactoring
@Test
public void shouldAnswerHttpCall() throws Exception {
new Boss(PORT);
assertThat(Http.callOn(PORT), is(OK));
}
9. • Se non posso parlare di un’entità non posso farne
il design
• Per fare il design di un sistema ho bisogno di “new
Sistema()”
• Quindi, l’application server, se c’e’, deve essere un
dettaglio implementativo
11. Il Secondo Test
@Test
public void shouldAnswerWithAnHtmlDocument() throws Exception {
new Boss(PORT);
assertThat(Http.callOn(PORT),
is(HttpAnswer.with("<html><body></body></html>")));
}
public class Boss {
...
public Boss(int port) throws IOException, InstantiationException {
selector = new SelectorThread();
selector.setPort(port);
HttpAnswer answer = HttpAnswer.with("<html><body></body></html>");
selector.setAdapter(new AlwaysReturn(answer));
selector.listen();
}
...
}
12. Tutto verde?
@Test
public void shouldAnswerWithAnHtmlDocument() {
new Boss(PORT);
assertThat(Http.callOn(PORT),
is(HttpAnswer.with("<html><body></body></html>")));
}
@Test
public void shouldAnswerHttpCall() {
new Boss(PORT);
assertThat(Http.callOn(PORT), is(OK));
}
13. Sfruttare le microdivergenze
Il concetto di Screen nasce per conciliare due tests
@Test
public void shouldAnswerHttpCall() throws Exception {
Boss boss = new Boss(PORT, new BlankScreen());
HttpAnswer answer = Http.callOn(PORT);
boss.stop();
assertThat(answer, is(HttpAnswer.ok()));
}
@Test
public void shouldAnswerWithAnHtmlDocument() throws Exception {
Boss boss = new Boss(PORT, new HtmlScreen());
HttpAnswer answer = Http.callOn(PORT);
boss.stop();
assertThat(answer, is(HttpAnswer.with("<html><body></body></html>")));
}
14. La Nascita Della Gui
Il Movimento Verso il Basso
public class HtmlScreenBehavior {
@Test
public void shouldRenderAnHtmlDocument() {
assertThat(new HtmlScreen().render(), is("<html><body></body></html>"));
}
}
L’Asserzione a Specchio
@Test
public void shouldAnswerWithTheScreenContents() throws Exception {
Screen screen = new HtmlScreen();
boss = new Boss(PORT, screen);
assertThat(Http.callOn(PORT), is(HttpAnswer.with(screen.render())));
}
15. Un Edificio
La grafica vettoriale secondo Raphael
<html>
<head>
<script type="text/javascript" src="raphael-min.js"></script>
<script type="text/javascript" charset="utf-8">
window.onload = function() {
var map = new Raphael(0,0,600,400);
map.rect(10,10,50,40);
};
</script>
</head>
<body>
</body>
</html>
16. Testare un sacco di sintassi?
@Test
public void shouldRenderABuildingAsARectangle() {
assertThat(new HtmlScreen().render(), is("<html>n" +
"<head>n" +
" <script type="text/javascript" src="raphael-min.js"></script>n" +
" <script type="text/javascript" charset="utf-8">n" +
" window.onload = function() {n" +
" var map = new Raphael(0,0,600,400);n" +
" var building = map.rect(10,10,50,40);n" +
" };n" +
" </script>n" +
"</head>n" +
"<body>n" +
"</body>n" +
"</html>"));
}
17. O dichiarare il comportamento?
@Test
public void shouldRenderABuildingAsARectangle() throws Exception {
User user = new User().lookAt(new HtmlScreen().render());
assertThat(user.currentSight(),
is("A Rectangle at [10,10], 40px high and 50px wide"));
}
Cosa voglio veramente dichiarare?
18. Si, bello, ma come?
function Raphael(x, y, width, height){
this.rect = function(x, y, width, height) {
output = "A Rectangle at [" + x + "," + y + "], " +
height + "px high and " + width + "px wide";
}
}
var output = “”;
Come stubbare una libreria di grafica vettoriale?
function Window() {
}
var window = new Window();
Come stubbare un browser?
19. Ma chi e’ ‘sto User?
public class User {
…
public User lookAt(String htmlPage) {
JavaScriptSource source = new XomJavaScriptSource(htmlPage);
source.evaluateWith(javaScript);
triggerOnLoad();
result.readOutput(javaScript);
return this;
}
…
}
20. Il risultato
public class HtmlScreen implements Screen {
private Building building = new Building(10, 10, 40, 50);
public String render() {
String start = "<html><head>" +
" <script type="text/javascript" src="raphael-min.js"></script>" +
" <script type="text/javascript" charset="utf-8">" +
" window.onload = function() {" +
" var map = new Raphael(0,0,600,400);";
String end = "}</script></head><body></body></html>";
return start + building.render() + end;
}
21. Mantenere sempre l’omogeneità
public class HtmlScreen {
...
public String render() {
return header() +
vectorGraphics.include() +
openScript() +
openFunction() +
vectorGraphics.init() +
building.render(vectorGraphics) +
closeFunction() +
closeScript() +
ending();
}
...
}
22. Tanti Edifici!
@Test
public void shouldRenderMultipleBuildings() throws Exception {
HtmlScreen htmlScreen = new HtmlScreen();
htmlScreen.addBuilding(10,10,50,40);
htmlScreen.addBuilding(80,10,30,40);
htmlScreen.addBuilding(10,70,100,40);
User user = new User().lookAt(htmlScreen.render());
assertThat(user.currentSight(), is(aRectangle(10, 10, 50, 40)));
assertThat(user.currentSight(), is(aRectangle(80, 10, 30, 40)));
assertThat(user.currentSight(), is(aRectangle(10, 70, 100, 40)));
}
23. Il Personaggio
@Test
public void shouldRenderACharacter() {
HtmlScreen htmlScreen = new HtmlScreen().addCharacter(70,60);
User user = new User().lookAt(htmlScreen.render());
assertThat(user.currentSight(), is(aCircle(70, 60, 5)));
}
24. Generalizzazione
private String renderViewElements() {
String renderedElements = "";
for (ViewElement element : viewElements) {
renderedElements += element.render(vectorGraphics);
}
return renderedElements;
}
L’HtmlScreen smette di parlare di buildings e characters
public HtmlScreen addViewElement(ViewElement element) {
viewElements.add(element);
return this;
}
25. Un’ultima volta verso il basso
@Test
public void shouldBeARedCircle() {
GameCharacter character = new GameCharacter(10,10);
VectorGraphics vectorGraphics = new TextualVectorGraphics();
assertThat(character.render(vectorGraphics),
is("A Circle at [10,10], 5px in diameter"));
}
public class TextualVectorGraphics implements VectorGraphics {
@Override
public String rect(int x, int y, int width, int height) {
return aRectangle(x,y,width,height);
}
@Override
public String circle(int x, int y, int size) {
return aCircle(x,y,size);
}
}
26. Questo non riguarda solo la vista
Un’occhiata al futuro di questa applicazione…
Interazione :
@Test
public void characterShouldMoveOnClick() {
String page = Http.callOn(PORT).payload();
user.lookAt(page);
user.clickOn(60, 60);
page = Http.callOn(PORT).payload();
user.lookAt(page);
assertThat(user.currentSight, is("A Circle at [60,60], 5px in diameter"));
}
Il protocollo si stacca dalla logica del gioco :
@Test
public void shouldAnswerHttpCall() throws Exception {
httpServer = new HttpServer(PORT).publish(boss);
assertThat(Http.callOn(PORT), is(HttpAnswer.ok()));
}
27. Questo non riguarda solo la vista
Avendo le buone primitive ogni asserzione e’ possibile…
Arriva la persistenza :
@Test
public void characterPositionShouldPersist() {
Boss boss1 = new Boss(persistence, screen1);
boss1.moveCharacterTo(30,30);
Boss boss2 = new Boss(persistence, screen2);
assertThat(screen2.render(), is("A Circle at [30,30], 5px in diameter"));
}
Ajax :
@Test
public void characterShouldMoveOnClick() {
page = new HtmlScreen().render();
user.lookAt(page);
user.clickOn(60, 60);
assertThat(user.currentSight, is("A Circle at [60,60], 5px in diameter"));
}