Thursday, February 3, 2011

FLASH: TPP

Flash Cards: T-P Premise

I read a nice blog today exploring the Transformation Priority Premise using the flash card kata. The author did it in both lisp and C#. Let’s try it in Java.

The flash card kata is pretty straightforward. Given a set of questions and answers, ask the user each question and solicit an answer. If the answer is correct, say so and count it. If the answer is wrong show the correct answer and count the error. At the end, print the number of right and wrong answers.

The first test is wonderfully simple. Given an empty list of questions and answers, end the game immediately with a summary showing nothing right and nothing wrong.

We begin with an empty test which passes:

public class flashCardTest { @Test public void degenerateGame() throws Exception { } }

We get the test to fail by asserting the answer we want and by doing a couple of ({}–>nil) transforms. (In this case -1 is the integer equivalent of nil, e.g. a degenerate value)

public class FlashCardTest { @Test public void rightAndWrongShouldBeZeroIfGamePlayedWithNoCards() { List emptyList = new ArrayList(); playGame(emptyList); assertEquals(0, rightAnswers()); assertEquals(0, wrongAnswers()); }  private int wrongAnswers() { return -1; }  private int rightAnswers() { return -1; }  private void playGame(List flashCards) { } }

To get this to pass just need to do some (nil->constant) transforms.

private int wrongAnswers() { return 0; }  private int rightAnswers() { return 0; }

This solution is a bit ugly since it couples the test and the solution. So let’s refactor to create a class.

public class FlashCardTest { @Test public void rightAndWrongShouldBeZeroIfGamePlayedWithNoCards() { FlashCardGame flashCardGame = new FlashCardGame(); List emptyList = new ArrayList();  flashCardGame.playGame(emptyList);  assertEquals(0, flashCardGame.rightAnswers()); assertEquals(0, flashCardGame.wrongAnswers()); } }

public class FlashCardGame { public FlashCardGame() { }  int wrongAnswers() { return 0; }  int rightAnswers() { return 0; }  void playGame(List flashCards) { } }

For the next test, lets try a game with a single flash card, that the user gets right.

@Test public void rightShouldBeOneIfOneRightAnswer() { FlashCard card = new FlashCard("Q", "A"); List cards = new ArrayList(); cards.add(card);  flashCardGame.playGame(cards); assertEquals(1, flashCardGame.rightAnswers()); assertEquals(0, flashCardGame.wrongAnswers()); }

This fails of course. We can make it pass by simply incrementing the right count in playGame if the list of cards is not zero. This is a (unconditional->if) transform. That, plus a little refactoring gives us:

public class FlashCardGame { private int rightAnswers;  public FlashCardGame() { }  int getWrongAnswers() { return 0; }  int getRightAnswers() { return rightAnswers; }  void playGame(List flashCards, FlashCardTest answerer) { if (flashCards.size() != 0) rightAnswers++; } }

OK, so let’s try a wrong answer.

@Test public void wrongShouldBeOneIfOneWrongAnswer() { FlashCard card = new FlashCard("QW", "A"); List cards = new ArrayList(); cards.add(card);  flashCardGame.playGame(cards); assertEquals(0, flashCardGame.getRightAnswers()); assertEquals(1, flashCardGame.getWrongAnswers()); }  public String answerQuestion(String question) { if (question.equals("QR")) return "A"; else return "W"; }

This forced us to create the answerQuestion function that pretends to be a user answering questions. If you pass in “QR” you get the right answer “A”. If you pass in “QW” you get the wrong answer “W”. To get this test to pass we’re going to have to get this function called by playGame. We can do this by passing the test along in an argument using the Change Signature refactoring. Then we can use a (unconditional->if) transform to check the value of our new function.

public class FlashCardGame { private int rightAnswers; private int wrongAnswers;  public FlashCardGame() { }  int getWrongAnswers() { return wrongAnswers; }  int getRightAnswers() { return rightAnswers; }  void playGame(List flashCards, FlashCardTest answerer) { if (flashCards.size() != 0) { String question = flashCards.get(0).getQuestion(); if (answerer.answerQuestion(question).equals("A")) rightAnswers++; else wrongAnswers++; } } }

Of course this is hideous, so we need to refactor alot.

public interface User { String answerQuestion(String question); }  ----  public class MockUser implements User { public MockUser() { }  public String answerQuestion(String question) { if (question.equals("QR")) return "A"; else return "W"; } }  ----  public class FlashCardGame { private int rightAnswers; private int wrongAnswers;  public FlashCardGame() { }  int getWrongAnswers() { return wrongAnswers; }  int getRightAnswers() { return rightAnswers; }  void playGame(List flashCards, User user) { if (flashCards.size() != 0) { String question = flashCards.get(0).getQuestion(); String answer = user.answerQuestion(question); if (answer.equals("A")) rightAnswers++; else wrongAnswers++; } } }  ----  public class FlashCardTest { private FlashCardGame flashCardGame; private MockUser user = new MockUser();  @Before public void setUp() throws Exception { flashCardGame = new FlashCardGame(); user = new MockUser(); }  @Test public void rightAndWrongShouldBeZeroIfGamePlayedWithNoCards() { List emptyList = new ArrayList();  flashCardGame.playGame(emptyList, user);  assertEquals(0, flashCardGame.getRightAnswers()); assertEquals(0, flashCardGame.getWrongAnswers()); }  @Test public void rightShouldBeOneIfOneRightAnswer() { FlashCard card = new FlashCard("QR", "A"); List cards = new ArrayList(); cards.add(card);  flashCardGame.playGame(cards, user); assertEquals(1, flashCardGame.getRightAnswers()); assertEquals(0, flashCardGame.getWrongAnswers()); }  @Test public void wrongShouldBeOneIfOneWrongAnswer() { FlashCard card = new FlashCard("QW", "A"); List cards = new ArrayList(); cards.add(card);  flashCardGame.playGame(cards, user); assertEquals(0, flashCardGame.getRightAnswers()); assertEquals(1, flashCardGame.getWrongAnswers()); } }

Now let’s do two questions, one right and one wrong.

@Test public void countBothOneRightAndOneWrong() { List cards = new ArrayList(); cards.add(new FlashCard("QW", "A")); cards.add(new FlashCard("QR", "A"));  flashCardGame.playGame(cards, user); assertEquals(1, flashCardGame.getRightAnswers()); assertEquals(1, flashCardGame.getWrongAnswers()); }

This fails, but we can make it pass with a (if->while) transform.

void playGame(List flashCards, User user) { for (FlashCard card : flashCards) { String question = card.getQuestion(); String answer = user.answerQuestion(question); if (answer.equals("A")) rightAnswers++; else wrongAnswers++; } }

One thing left. We need to actually compare the answer in the flashcard to the response from the user.

@Test public void countThreeNewQuestionsTwoRightOneWrong() { List cards = new ArrayList(); cards.add(new FlashCard("Q1", "1")); cards.add(new FlashCard("Q2", "2")); cards.add(new FlashCard("Q3", "wrong"));  flashCardGame.playGame(cards, user); assertEquals(2, flashCardGame.getRightAnswers()); assertEquals(1, flashCardGame.getWrongAnswers()); }

We need to make a small change to the Mock.

public class MockUser implements User { public String answerQuestion(String question) { if (question.equals("QR")) return "A"; else { return question.substring(1); } } }

And now we can make this pass with a simple (expression->function) transform.

void playGame(List flashCards, User user) { for (FlashCard card : flashCards) { String question = card.getQuestion(); String answer = user.answerQuestion(question); if (answer.equals(card.getAnswer())) rightAnswers++; else wrongAnswers++; } }

There’s more to do, of course, but the plumbing is all set up, and the algorithm looks right. There were several cases where we could have used a lower transform such as (variable->assignment) but there was no need, and the algorithm came out nicely.

There is just the slightest chance that the one use of (if->while) could have been done with (statement->tail-recursion), but since this is Java, that’s probably not the best choice.

5 comments:

  1. The code blocks are not readable, they have no line breaks

    ReplyDelete
  2. Thanks for some useful tips in programming.

    Nowadays the most desired type of solutions is blackberry development that allows to develop applications for mobile phone. All of them needs to be checked by quality control testing.

    ReplyDelete
  3. Thanks for great posts. Let me mention about cheap home insurance with discounts homeowners insurance agents. Save on free rates on homeowners insurance.

    ReplyDelete
  4. Thank you for posting this great info. You have great chance to get info about online affiliate programs. The most common casino affiliate programs such as go wild affiliates and great poker rooms such as redbet affiliate.

    ReplyDelete
  5. I'm really impressed with your writing skills and also with the layout on your weblog. Either way keep up the excellent quality writing, it is rare to see a great blog like this one these days..
    www.coderstech.com
    www.codersiq.com

    ReplyDelete