Flash - TPP

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.

Robert Martin is a former 8th Light employee.

Interested in 8th Light's services? Let's talk.

Contact Us