Tennis Refactoring

Level: Intermediate 30–60 min

Concepts: RefactoringLegacy CodeStrings

Solutions: C# | TypeScript | Python


You are given a working tennis scoring function. The code is correct but hard to read. Your job is to refactor it into something clean — without breaking existing behavior.

This is a refactoring kata. Write characterization tests first, then improve the code.

Tennis Scoring Rules

  • A game starts at 0-0 (called “Love-All”)
  • Scores go: 0 (“Love”), 1 (“Fifteen”), 2 (“Thirty”), 3 (“Forty”)
  • If both players have 40, it’s “Deuce”
  • If the score is Deuce and a player scores, they have “Advantage”
  • If the player with Advantage scores again, they win
  • If the other player scores when their opponent has Advantage, it goes back to Deuce

The Legacy Code

function getScore(p1Score, p2Score, p1Name, p2Name) {
  var result = "";
  if (p1Score == p2Score) {
    if (p1Score == 0) result = "Love-All";
    else if (p1Score == 1) result = "Fifteen-All";
    else if (p1Score == 2) result = "Thirty-All";
    else if (p1Score >= 3) result = "Deuce";
  } else if (p1Score >= 4 || p2Score >= 4) {
    var diff = p1Score - p2Score;
    if (diff == 1) result = "Advantage " + p1Name;
    else if (diff == -1) result = "Advantage " + p2Name;
    else if (diff >= 2) result = "Win for " + p1Name;
    else if (diff <= -2) result = "Win for " + p2Name;
  } else {
    var scores = ["Love", "Fifteen", "Thirty", "Forty"];
    result = scores[p1Score] + "-" + scores[p2Score];
  }
  return result;
}

Your Task

Step 1: Write characterization tests. Cover every scoring combination. Your tests should capture the exact behavior of the current code.

Step 2: Refactor. Improve readability, extract concepts, remove duplication. Keep all tests green throughout.

Test Cases

Player 1 ScorePlayer 2 ScoreOutput
00Love-All
11Fifteen-All
22Thirty-All
33Deuce
44Deuce
10Fifteen-Love
01Love-Fifteen
21Thirty-Fifteen
31Forty-Fifteen
43Advantage Player1
34Advantage Player2
54Advantage Player1
53Win for Player1
35Win for Player2

Bonus

  • Refactor to use polymorphism — replace conditionals with a scoring strategy per game state
  • Add a TennisGame class that tracks points via wonPoint(playerName) and reports score via getScore()
  • Support tiebreak scoring (numeric, first to 7, win by 2)

Hint

The code has three distinct states: equal scores, endgame (scores ≥ 40), and normal play. Each state has different formatting rules. Extracting these into named methods or separate functions makes the logic immediately clearer.

Reference Walkthrough

Reference implementations in C#, TypeScript, and Python live at tddbuddy-reference-katas/tennis-refactoring. This is an F2 refactoring kata classified characterization test set only: the legacy function’s behavior is captured in a shared contract (SCENARIOS.md) that every language satisfies byte-for-byte, and each implementation refactors the legacy’s nested conditionals into three named branches (equal / endgame / in-play) with POINT_NAMES lifting the magic ["Love","Fifteen","Thirty","Forty"] array into a named lookup.

No builder in this one. Unlike Calc Refactor (the other F2 refactoring kata), tennis scoring is a pure four-argument function — a MatchBuilder would cost more lines than the scenarios it replaces. The F2 discipline here is the shared characterization contract itself.

Cousin kata — Tennis Score is the Pedagogy mode twin: same domain, different pedagogical question. Tennis Score shows how a clean tennis scorer emerges from TDD low-gear triangulation through the state-machine refactor at Advantage. Tennis Refactoring starts from legacy code and gets to the same destination via characterization tests. Pick Tennis Score to practice the TDD rhythm; pick Tennis Refactoring to practice the legacy-code-then-clean-code move.

This kata ships in Agent Full-Bake mode at middle gear, the F2 tier’s characterization test set only sub-classification. See the repo’s Gears section for why middle gear is the deliberate choice once the legacy’s promises are written down.


  • Katas Are Rehearsal, Not Performance
    You don't practice TDD on production code. You practice on katas and bring the muscle memory to production. The gap between knowing TDD and doing TDD is reps.
  • Agents Amplify Whatever Vocabulary They Find
    Agents do not bring vocabulary to your codebase. They mirror what's already there. Strong vocabulary gets agent contributions that read like the team. Weak vocabulary gets faster mediocrity. The work you did for craft reasons just became leverage.
  • The Hidden Output of TDD Was Never Code
    The visible artifact of TDD is a passing test suite. The actual artifact, the one that compounded value over the life of the codebase, was the vocabulary the team built while writing it. The dictionary, not the regression net.