January
- Pan, educación, libertad (Ψωμί, παιδεία, ελευθερία), Petros Márkaris
- NW London, Zadie Smith
- 101 cuentos clásicos de la China, Chang Shiru, Ramiro A. Calle
- Mr. Neighborly's Humble Little Ruby Book, Jeremy McAnally
February
- Blankets, Craig Thompson
March
- Practical Object-Oriented Design in Ruby, Sandi Metz
- Modern C++ Programming with Test-Driven Development, Jeff Langr
April
- Learning PHP, MySQL & JavaScript, Robin Nixon
- Measuring the world, Daniel Kehlmann
Record of experiments, readings, links, videos and other things that I find on the long road.
Registro de experimentos, lecturas, links, vídeos y otras cosas que voy encontrando en el largo camino.
Monday, April 28, 2014
Wednesday, April 23, 2014
Interesting Talk: "Test-Driven Development (that’s not what we meant)"
I've just watched this amazing talk by Steve Freeman:
These are the talk slides.
Tuesday, April 22, 2014
Interesting Talk: "Exploring a Legacy Clojure Codebase"
I've just watched this interesting talk by Jon Neale and Ragnar Dahlen:
Sunday, April 20, 2014
Interesting Talk: "The SOLID Design Principles Deconstructed"
I've just watched this great talk by Kevlin Henney:
Saturday, April 19, 2014
Interesting Talk: "Functional Principles for Object Oriented Development"
I've just watched this interesting talk by Jessica Kerr:
This is the Ruby version. There's also a Java version of this talk.
Thursday, April 17, 2014
Interesting KataCast: "Clojure TDD demo (Robozzle)"
I've just watched this great explained katacast by Brian Marick
The top-down style Marick uses to grow his code through TDD reminds me a lot of how Gregor Kiczales worked through the example programs in Racket of his Introduction to Systematic Program Design - Part 1 Coursera course that I took a while ago.
The difference between the two processes is basically in the tooling, because Midje allows Marick to "stub" the results of helper functions so he can get feedback about his top functions from the very beginning whereas in Kiczales' examples he had to wait until all the helpers were working to see the top functions tests pass.
Apart from that they use the same top-down approach.
Interesting Talk: "Some Thoughts on Classes After 18 Months of Clojure"
I've just watched this great talk by Brian Marick:
Sunday, April 13, 2014
Interesting Talk: "Objects are Just Objects, Aren't they?"
I've just watched this great talk by Rick DeNatale:
Friday, April 11, 2014
Standard Output redirection in Java for a characterization test
Last weekend I did the UglyTrivia legacy code refactoring kata in Java.
Before starting to refactor, I added a characterization test to describe (characterize) the actual behavior of the original code. Since the only "visible effects" of the code were the lines it was writing to the standard output, I had to use that output to create the characterization test.
This is the code of the characterization test:
Although in this final version, the expected output reflects the fixing of two bugs in the original code, at the beginning it was just what the original code was writing to the standard output given a fixed seed for the random number generator.
This article describes this technique very well: Working Effectively With Characterization Tests.
To be able to redirect the standard output I used the StandardOutputRedirection class:
The characterization test shown before yielded a code coverage of 93% which gave me confidence enough to start refactoring the code.
Before starting to refactor, I added a characterization test to describe (characterize) the actual behavior of the original code. Since the only "visible effects" of the code were the lines it was writing to the standard output, I had to use that output to create the characterization test.
This is the code of the characterization test:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.adaptionsoft.games.trivia; | |
import static org.junit.Assert.assertEquals; | |
import java.io.ByteArrayOutputStream; | |
import java.util.Random; | |
import org.junit.After; | |
import org.junit.Before; | |
import org.junit.Test; | |
import com.adaptionsoft.games.uglytrivia.Board; | |
import com.adaptionsoft.games.uglytrivia.Dice; | |
import com.adaptionsoft.games.uglytrivia.Game; | |
import com.adaptionsoft.games.uglytrivia.Players; | |
import com.adaptionsoft.games.uglytrivia.Questions; | |
import com.adaptionsoft.games.uglytrivia.Rules; | |
public class RunCharacterizationTest { | |
StandardOutputRedirection outputRedirection; | |
@Before | |
public void setUp() { | |
outputRedirection = new StandardOutputRedirection(new ByteArrayOutputStream(), System.out); | |
} | |
@After | |
public void tearDown() { | |
outputRedirection.reset(); | |
} | |
@Test | |
public void gameCharacterizationForSeed1234() { | |
Game game = createGame(1234); | |
game.run(); | |
assertEquals(expectedOutput(), outputRedirection.getRedirectedOutput()); | |
} | |
private Game createGame(int seed) { | |
Random rand = new Random(seed); | |
Game game = new Game(rand, | |
new Players("Chet", "Pat", "Sue"), | |
new Rules(), new Questions(), | |
new Board(), new Dice(rand)); | |
return game; | |
} | |
private String expectedOutput() { | |
String expectedOutput = "Chet was added\n"; | |
expectedOutput += "They are player number 1\n"; | |
expectedOutput += "Pat was added\n"; | |
expectedOutput += "They are player number 2\n"; | |
expectedOutput += "Sue was added\n"; | |
expectedOutput += "They are player number 3\n"; | |
expectedOutput += "Chet is the current player\n"; | |
expectedOutput += "They have rolled a 4\n"; | |
expectedOutput += "Chet's new location is 4\n"; | |
expectedOutput += "The category is Pop\n"; | |
expectedOutput += "Pop Question 0\n"; | |
expectedOutput += "Answer was correct!!!!\n"; | |
expectedOutput += "Chet now has 1 Gold Coins.\n"; | |
expectedOutput += "Pat is the current player\n"; | |
expectedOutput += "They have rolled a 4\n"; | |
expectedOutput += "Pat's new location is 4\n"; | |
expectedOutput += "The category is Pop\n"; | |
expectedOutput += "Pop Question 1\n"; | |
expectedOutput += "Answer was correct!!!!\n"; | |
expectedOutput += "Pat now has 1 Gold Coins.\n"; | |
expectedOutput += "Sue is the current player\n"; | |
expectedOutput += "They have rolled a 1\n"; | |
expectedOutput += "Sue's new location is 1\n"; | |
expectedOutput += "The category is Science\n"; | |
expectedOutput += "Science Question 0\n"; | |
expectedOutput += "Answer was correct!!!!\n"; | |
expectedOutput += "Sue now has 1 Gold Coins.\n"; | |
expectedOutput += "Chet is the current player\n"; | |
expectedOutput += "They have rolled a 5\n"; | |
expectedOutput += "Chet's new location is 9\n"; | |
expectedOutput += "The category is Science\n"; | |
expectedOutput += "Science Question 1\n"; | |
expectedOutput += "Answer was correct!!!!\n"; | |
expectedOutput += "Chet now has 2 Gold Coins.\n"; | |
expectedOutput += "Pat is the current player\n"; | |
expectedOutput += "They have rolled a 3\n"; | |
expectedOutput += "Pat's new location is 7\n"; | |
expectedOutput += "The category is Rock\n"; | |
expectedOutput += "Rock Question 0\n"; | |
expectedOutput += "Question was incorrectly answered\n"; | |
expectedOutput += "Pat was sent to the penalty box\n"; | |
expectedOutput += "Sue is the current player\n"; | |
expectedOutput += "They have rolled a 1\n"; | |
expectedOutput += "Sue's new location is 2\n"; | |
expectedOutput += "The category is Sports\n"; | |
expectedOutput += "Sports Question 0\n"; | |
expectedOutput += "Answer was correct!!!!\n"; | |
expectedOutput += "Sue now has 2 Gold Coins.\n"; | |
expectedOutput += "Chet is the current player\n"; | |
expectedOutput += "They have rolled a 3\n"; | |
expectedOutput += "Chet's new location is 0\n"; | |
expectedOutput += "The category is Pop\n"; | |
expectedOutput += "Pop Question 2\n"; | |
expectedOutput += "Answer was correct!!!!\n"; | |
expectedOutput += "Chet now has 3 Gold Coins.\n"; | |
expectedOutput += "Pat is the current player\n"; | |
expectedOutput += "They have rolled a 5\n"; | |
expectedOutput += "Pat is getting out of the penalty box\n"; | |
expectedOutput += "Pat's new location is 0\n"; | |
expectedOutput += "The category is Pop\n"; | |
expectedOutput += "Pop Question 3\n"; | |
expectedOutput += "Answer was correct!!!!\n"; | |
expectedOutput += "Pat now has 2 Gold Coins.\n"; | |
expectedOutput += "Sue is the current player\n"; | |
expectedOutput += "They have rolled a 1\n"; | |
expectedOutput += "Sue's new location is 3\n"; | |
expectedOutput += "The category is Rock\n"; | |
expectedOutput += "Rock Question 1\n"; | |
expectedOutput += "Answer was correct!!!!\n"; | |
expectedOutput += "Sue now has 3 Gold Coins.\n"; | |
expectedOutput += "Chet is the current player\n"; | |
expectedOutput += "They have rolled a 3\n"; | |
expectedOutput += "Chet's new location is 3\n"; | |
expectedOutput += "The category is Rock\n"; | |
expectedOutput += "Rock Question 2\n"; | |
expectedOutput += "Answer was correct!!!!\n"; | |
expectedOutput += "Chet now has 4 Gold Coins.\n"; | |
expectedOutput += "Pat is the current player\n"; | |
expectedOutput += "They have rolled a 5\n"; | |
expectedOutput += "Pat's new location is 5\n"; | |
expectedOutput += "The category is Science\n"; | |
expectedOutput += "Science Question 2\n"; | |
expectedOutput += "Question was incorrectly answered\n"; | |
expectedOutput += "Pat was sent to the penalty box\n"; | |
expectedOutput += "Sue is the current player\n"; | |
expectedOutput += "They have rolled a 3\n"; | |
expectedOutput += "Sue's new location is 6\n"; | |
expectedOutput += "The category is Sports\n"; | |
expectedOutput += "Sports Question 1\n"; | |
expectedOutput += "Answer was correct!!!!\n"; | |
expectedOutput += "Sue now has 4 Gold Coins.\n"; | |
expectedOutput += "Chet is the current player\n"; | |
expectedOutput += "They have rolled a 4\n"; | |
expectedOutput += "Chet's new location is 7\n"; | |
expectedOutput += "The category is Rock\n"; | |
expectedOutput += "Rock Question 3\n"; | |
expectedOutput += "Answer was correct!!!!\n"; | |
expectedOutput += "Chet now has 5 Gold Coins.\n"; | |
expectedOutput += "Pat is the current player\n"; | |
expectedOutput += "They have rolled a 3\n"; | |
expectedOutput += "Pat is getting out of the penalty box\n"; | |
expectedOutput += "Pat's new location is 8\n"; | |
expectedOutput += "The category is Pop\n"; | |
expectedOutput += "Pop Question 4\n"; | |
expectedOutput += "Answer was correct!!!!\n"; | |
expectedOutput += "Pat now has 3 Gold Coins.\n"; | |
expectedOutput += "Sue is the current player\n"; | |
expectedOutput += "They have rolled a 5\n"; | |
expectedOutput += "Sue's new location is 11\n"; | |
expectedOutput += "The category is Rock\n"; | |
expectedOutput += "Rock Question 4\n"; | |
expectedOutput += "Answer was correct!!!!\n"; | |
expectedOutput += "Sue now has 5 Gold Coins.\n"; | |
expectedOutput += "Chet is the current player\n"; | |
expectedOutput += "They have rolled a 4\n"; | |
expectedOutput += "Chet's new location is 11\n"; | |
expectedOutput += "The category is Rock\n"; | |
expectedOutput += "Rock Question 5\n"; | |
expectedOutput += "Answer was correct!!!!\n"; | |
expectedOutput += "Chet now has 6 Gold Coins.\n"; | |
return expectedOutput; | |
} | |
} |
Although in this final version, the expected output reflects the fixing of two bugs in the original code, at the beginning it was just what the original code was writing to the standard output given a fixed seed for the random number generator.
This article describes this technique very well: Working Effectively With Characterization Tests.
To be able to redirect the standard output I used the StandardOutputRedirection class:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.adaptionsoft.games.trivia; | |
import java.io.ByteArrayOutputStream; | |
import java.io.PrintStream; | |
public class StandardOutputRedirection { | |
private ByteArrayOutputStream newOut; | |
private PrintStream old_out; | |
public StandardOutputRedirection( | |
ByteArrayOutputStream newOut, | |
PrintStream old_out) { | |
super(); | |
this.newOut = newOut; | |
this.old_out = old_out; | |
System.setOut(new PrintStream(this.newOut)); | |
} | |
public void reset() { | |
System.setOut(old_out); | |
} | |
public String getRedirectedOutput() { | |
return new String(newOut.toByteArray()); | |
} | |
} |
The characterization test shown before yielded a code coverage of 93% which gave me confidence enough to start refactoring the code.
Labels:
Java,
Katas,
Learning,
Legacy Code,
Refactoring,
Tests
Tuesday, April 8, 2014
Interesting Talk: "AngularJS in 20ish Minutes"
Yesterday morning I watched this very interesting short talk about AngularJS by Dan Wahlin:
Sunday, April 6, 2014
Kata: UglyTrivia legacy code refactoring kata in Java
I've just solved the UglyTrivia legacy code refactoring kata in Java which was the kata we did in the last Barcelona Software Craftsmanship event.
This is the initial version of the Game class:
And this is the code after my refactoring:
Update:
And this is the code after I revisited it again (Feb. 27th 2016):
Check my solution in GitHub with commits after each refactoring step.
This is the initial version of the Game class:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.adaptionsoft.games.uglytrivia; | |
import java.util.ArrayList; | |
import java.util.LinkedList; | |
public class Game { | |
ArrayList players = new ArrayList(); | |
int[] places = new int[6]; | |
int[] purses = new int[6]; | |
boolean[] inPenaltyBox = new boolean[6]; | |
LinkedList popQuestions = new LinkedList(); | |
LinkedList scienceQuestions = new LinkedList(); | |
LinkedList sportsQuestions = new LinkedList(); | |
LinkedList rockQuestions = new LinkedList(); | |
int currentPlayer = 0; | |
boolean isGettingOutOfPenaltyBox; | |
public Game() { | |
for (int i = 0; i < 50; i++) { | |
popQuestions.addLast("Pop Question " + i); | |
scienceQuestions.addLast(("Science Question " + i)); | |
sportsQuestions.addLast(("Sports Question " + i)); | |
rockQuestions.addLast(createRockQuestion(i)); | |
} | |
} | |
public String createRockQuestion(int index) { | |
return "Rock Question " + index; | |
} | |
public boolean isPlayable() { | |
return (howManyPlayers() >= 2); | |
} | |
public boolean add(String playerName) { | |
players.add(playerName); | |
places[howManyPlayers()] = 0; | |
purses[howManyPlayers()] = 0; | |
inPenaltyBox[howManyPlayers()] = false; | |
System.out.println(playerName + " was added"); | |
System.out.println("They are player number " + players.size()); | |
return true; | |
} | |
public int howManyPlayers() { | |
return players.size(); | |
} | |
public void roll(int roll) { | |
System.out.println(players.get(currentPlayer) + " is the current player"); | |
System.out.println("They have rolled a " + roll); | |
if (inPenaltyBox[currentPlayer]) { | |
if (roll % 2 != 0) { | |
isGettingOutOfPenaltyBox = true; | |
System.out.println(players.get(currentPlayer) | |
+ " is getting out of the penalty box"); | |
places[currentPlayer] = places[currentPlayer] + roll; | |
if (places[currentPlayer] > 11) | |
places[currentPlayer] = places[currentPlayer] - 12; | |
System.out.println(players.get(currentPlayer) + "'s new location is " | |
+ places[currentPlayer]); | |
System.out.println("The category is " + currentCategory()); | |
askQuestion(); | |
} else { | |
System.out.println(players.get(currentPlayer) | |
+ " is not getting out of the penalty box"); | |
isGettingOutOfPenaltyBox = false; | |
} | |
} else { | |
places[currentPlayer] = places[currentPlayer] + roll; | |
if (places[currentPlayer] > 11) | |
places[currentPlayer] = places[currentPlayer] - 12; | |
System.out.println(players.get(currentPlayer) + "'s new location is " | |
+ places[currentPlayer]); | |
System.out.println("The category is " + currentCategory()); | |
askQuestion(); | |
} | |
} | |
private void askQuestion() { | |
if (currentCategory() == "Pop") | |
System.out.println(popQuestions.removeFirst()); | |
if (currentCategory() == "Science") | |
System.out.println(scienceQuestions.removeFirst()); | |
if (currentCategory() == "Sports") | |
System.out.println(sportsQuestions.removeFirst()); | |
if (currentCategory() == "Rock") | |
System.out.println(rockQuestions.removeFirst()); | |
} | |
private String currentCategory() { | |
if (places[currentPlayer] == 0) | |
return "Pop"; | |
if (places[currentPlayer] == 4) | |
return "Pop"; | |
if (places[currentPlayer] == 8) | |
return "Pop"; | |
if (places[currentPlayer] == 1) | |
return "Science"; | |
if (places[currentPlayer] == 5) | |
return "Science"; | |
if (places[currentPlayer] == 9) | |
return "Science"; | |
if (places[currentPlayer] == 2) | |
return "Sports"; | |
if (places[currentPlayer] == 6) | |
return "Sports"; | |
if (places[currentPlayer] == 10) | |
return "Sports"; | |
return "Rock"; | |
} | |
public boolean wasCorrectlyAnswered() { | |
if (inPenaltyBox[currentPlayer]) { | |
if (isGettingOutOfPenaltyBox) { | |
System.out.println("Answer was correct!!!!"); | |
purses[currentPlayer]++; | |
System.out.println(players.get(currentPlayer) + " now has " + purses[currentPlayer] | |
+ " Gold Coins."); | |
boolean winner = didPlayerWin(); | |
currentPlayer++; | |
if (currentPlayer == players.size()) | |
currentPlayer = 0; | |
return winner; | |
} else { | |
currentPlayer++; | |
if (currentPlayer == players.size()) | |
currentPlayer = 0; | |
return true; | |
} | |
} else { | |
System.out.println("Answer was corrent!!!!"); | |
purses[currentPlayer]++; | |
System.out.println(players.get(currentPlayer) + " now has " + purses[currentPlayer] | |
+ " Gold Coins."); | |
boolean winner = didPlayerWin(); | |
currentPlayer++; | |
if (currentPlayer == players.size()) | |
currentPlayer = 0; | |
return winner; | |
} | |
} | |
public boolean wrongAnswer() { | |
System.out.println("Question was incorrectly answered"); | |
System.out.println(players.get(currentPlayer) + " was sent to the penalty box"); | |
inPenaltyBox[currentPlayer] = true; | |
currentPlayer++; | |
if (currentPlayer == players.size()) | |
currentPlayer = 0; | |
return true; | |
} | |
private boolean didPlayerWin() { | |
return !(purses[currentPlayer] == 6); | |
} | |
} |
And this is the code after my refactoring:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.adaptionsoft.games.uglytrivia; | |
public class Game { | |
private Rules rules; | |
private Players players; | |
private Player currentPlayer; | |
private Turn turn; | |
public Game(Players players, Rules rules, Turn turn) { | |
this.players = players; | |
this.rules = rules; | |
this.turn = turn; | |
} | |
public void run() { | |
presentPlayers(); | |
play(); | |
} | |
private void play() { | |
do { | |
nextPlayer(); | |
playTurn(); | |
} while (noPlayerHasWon()); | |
} | |
private void presentPlayers() { | |
int num = 0; | |
for (Player player : players) { | |
System.out.println(player + " was added"); | |
System.out.println("They are player number " + String.valueOf(++num)); | |
} | |
} | |
private void playTurn() { | |
turn.play(currentPlayer); | |
} | |
private void nextPlayer() { | |
currentPlayer = players.next(); | |
System.out.println(currentPlayer + " is the current player"); | |
} | |
private boolean noPlayerHasWon() { | |
return !currentPlayer.hasWonAccordingTo(rules); | |
} | |
} |
And this is the code after I revisited it again (Feb. 27th 2016):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.adaptionsoft.games.uglytrivia; | |
public class Game { | |
private Rules rules; | |
private Players players; | |
private Player currentPlayer; | |
private Turn turn; | |
public Game(Players players, Rules rules, Turn turn) { | |
this.players = players; | |
this.rules = rules; | |
this.turn = turn; | |
} | |
public void run() { | |
do { | |
nextPlayer(); | |
playTurn(); | |
} while (noPlayerHasWon()); | |
} | |
private void playTurn() { | |
turn.play(currentPlayer); | |
} | |
private void nextPlayer() { | |
currentPlayer = players.next(); | |
} | |
private boolean noPlayerHasWon() { | |
return !currentPlayer.hasWonAccordingTo(rules); | |
} | |
} |
Check my solution in GitHub with commits after each refactoring step.
Tuesday, April 1, 2014
Interesting Talk: "Testing Online Crazy Glue: Strategies for building testable PHP applications"
I've just watched this interesting talk by Chris Hartjes:
Introduced FormEmailService façade and struggling with names again
Following the advice given by One on a comment, I introduced a façade service that encapsulate everything about emails, FormEmailService with FormEmailComposer and EmailSender as collaborators. This simplified the creation of the IntroducedInformation class which was great.
I also followed his suggestion of passing the form object as a parameter to the sendByEmailAndRedirect method.
This was how it was used after that change:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
$info = | |
IntroducedInfoBuilder::aNewIntroducedInformation() | |
->onPage("empresas") | |
->toBeSentTo("xxx@ppp.es") | |
->build(); | |
$info->sendByEmailAndRedirect(new Form($_POST)); |
In the previous version the "information thing" was sending itself using a "Yoda-like" language:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
$info = | |
IntroducedInfoBuilder::aNewIntroducedInformation() | |
->onPage("empresas") | |
->toBeSentTo("xxx@ppp.es") | |
->build(); | |
$info->sendByEmailAndRedirect(); |
To keep the form as a parameter I decided to rename the class to so it was "something that does something on something" and not finding any better name I went down the road of "SomethingService" names:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
$service = | |
EmailAndRedirectServiceBuilder::aServiceToSendByEmail() | |
->aFormOnPage("empresas") | |
->to("xxx@ppp.es") | |
->build(); | |
$service->sendAndRedirect(new Form($_POST)); |
Well, it's not perfect but at least I think it reads better than the version where the "information thing" was sending a form.
This is how the class looks now:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class EmailAndRedirectService | |
{ | |
private $redirection; | |
private $emailService; | |
function __construct( | |
FormEmailService $emailService, | |
Redirection $redirection | |
) { | |
$this->emailService = $emailService; | |
$this->redirection = $redirection; | |
} | |
public function sendAndRedirect($form) | |
{ | |
if ($form->isNotFilled()) { | |
$this->redirection->backToInitialPage(); | |
return; | |
} | |
$email = $this->createEmail($form); | |
$sendingSucceeded = $this->send($email); | |
$this->redirect($sendingSucceeded); | |
} | |
private function redirect($sendingSucceeded) | |
{ | |
if ($sendingSucceeded) { | |
$this->redirection->toSuccessPage(); | |
} else { | |
$this->redirection->toFailPage(); | |
} | |
} | |
private function createEmail($form) | |
{ | |
return $this->emailService->composeEmailFrom($form); | |
} | |
private function send(Email $email) | |
{ | |
return $this->emailService->send($email); | |
} | |
} |
and these are its tests:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class EmailAndRedirectServiceTest extends PHPUnit_Framework_TestCase | |
{ | |
private $form; | |
private $redirection; | |
private $emailAndRedirectService; | |
private $email; | |
private $formEmailService; | |
function setUp() | |
{ | |
$this->redirection = Phake::mock('Redirection'); | |
$this->email = new Email("from", "to", "subject", "message"); | |
$this->form = Phake::mock('Form'); | |
$this->formEmailService = Phake::mock('FormEmailService'); | |
$this->emailAndRedirectService = | |
new EmailAndRedirectService( | |
$this->formEmailService, | |
$this->redirection | |
); | |
} | |
function testRedirectionBackToFormPage() | |
{ | |
$this->whenFormNotFilled(); | |
$this->emailAndRedirectService->sendAndRedirect($this->form); | |
Phake::verify($this->redirection) | |
->backToInitialPage(); | |
} | |
function testEmailComposition() | |
{ | |
$this->whenFormFilledWithWhateverFields(); | |
$this->andEmailGetsComposed(); | |
$this->emailAndRedirectService->sendAndRedirect($this->form); | |
Phake::verify($this->formEmailService) | |
->composeEmailFrom($this->form); | |
} | |
function testEmailSending() | |
{ | |
$this->whenFormFilledWithWhateverFields(); | |
$this->andEmailGetsComposed(); | |
$this->emailAndRedirectService->sendAndRedirect($this->form); | |
Phake::verify($this->formEmailService) | |
->send($this->email); | |
} | |
function testRedirectionToSuccesPage() | |
{ | |
$this->whenFormFilledWithWhateverFields(); | |
$this->andEmailGetsComposed(); | |
$this->andIsSentSuccessfully(); | |
$this->emailAndRedirectService->sendAndRedirect($this->form); | |
Phake::verify($this->redirection)->toSuccessPage(); | |
} | |
function testRedirectionToFailPage() | |
{ | |
$this->whenFormFilledWithWhateverFields(); | |
$this->andEmailGetsComposed(); | |
$this->butIsNotSentSuccessfully(); | |
$this->emailAndRedirectService->sendAndRedirect($this->form); | |
Phake::verify($this->redirection)->toFailPage(); | |
} | |
private function whenFormFilledWithWhateverFields() | |
{ | |
Phake::when($this->form) | |
->isNotFilled() | |
->thenReturn(false); | |
} | |
private function whenFormNotFilled() | |
{ | |
Phake::when($this->form) | |
->isNotFilled() | |
->thenReturn(true); | |
} | |
private function andIsSentSuccessfully() | |
{ | |
Phake::when($this->formEmailService) | |
->send($this->email) | |
->thenReturn(true); | |
} | |
private function butIsNotSentSuccessfully() | |
{ | |
Phake::when($this->formEmailService) | |
->send($this->email) | |
->thenReturn(false); | |
} | |
private function andEmailGetsComposed() | |
{ | |
Phake::when($this->formEmailService) | |
->composeEmailFrom($this->form) | |
->thenReturn($this->email); | |
} | |
} |
I keep thinking in a name for this that doesn't follow the "SomethingService" pattern, but until inspiration comes I'll settle on this one.
Thank you very much Carlos, Marcin and José for your feedback. I really appreciate it.
Subscribe to:
Posts (Atom)