From 4b3fc5685f0588f704b8190e3b37ee5eca9c2846 Mon Sep 17 00:00:00 2001 From: msauboua <msauboua@ippon.fr> Date: Mon, 22 Mar 2021 19:04:01 +0100 Subject: [PATCH] feat(kata-tennis): added tennis refactoring kata --- .gitlab-ci.yml | 1 + README.md | 1 + tennis/README.md | 54 ++++++++ tennis/refactoring/.gitignore | 6 + tennis/refactoring/.gitlab-ci.yml | 11 ++ tennis/refactoring/pom.xml | 39 ++++++ .../main/java/fr/ippon/kata/TennisGame.java | 7 + .../main/java/fr/ippon/kata/TennisGame1.java | 78 +++++++++++ .../main/java/fr/ippon/kata/TennisGame2.java | 125 ++++++++++++++++++ .../main/java/fr/ippon/kata/TennisGame3.java | 38 ++++++ .../fr/ippon/kata/TennisGameUnitTest.java | 64 +++++++++ .../src/test/resources/param-test-data.csv | 34 +++++ 12 files changed, 458 insertions(+) create mode 100644 tennis/README.md create mode 100644 tennis/refactoring/.gitignore create mode 100644 tennis/refactoring/.gitlab-ci.yml create mode 100644 tennis/refactoring/pom.xml create mode 100644 tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame.java create mode 100644 tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame1.java create mode 100644 tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame2.java create mode 100644 tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame3.java create mode 100644 tennis/refactoring/src/test/java/fr/ippon/kata/TennisGameUnitTest.java create mode 100644 tennis/refactoring/src/test/resources/param-test-data.csv diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cc25dcfd..2899f96d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,4 +29,5 @@ include: - local: "/leap-years/.gitlab-ci.yml" - local: "/employee-report/.gitlab-ci.yml" - local: "/java-concurrence/.gitlab-ci.yml" + - local: "/tennis/refactoring/.gitlab-ci.yml" - local: "/bowling-game/.gitlab-ci.yml" diff --git a/README.md b/README.md index 6bbf1136..86668740 100644 --- a/README.md +++ b/README.md @@ -59,3 +59,4 @@ Ce dépôt Git a pour but de partager les différents ateliers pouvant être ré | [Retour sur les 3 jours de conférences de DDD Europe](https://youtu.be/sDUuDpnLWXs) | Discussion | | | [Concurrence en Java](/java-concurrence) | Code&coffee | Enervé | | [Bowling Game](/bowling-game) | Kata | Moyen | +| [Tennis Refactoring kata](/tennis/refactoring) | Kata | Moyenne | diff --git a/tennis/README.md b/tennis/README.md new file mode 100644 index 00000000..882ff89d --- /dev/null +++ b/tennis/README.md @@ -0,0 +1,54 @@ +# Tennis coding and refactoring katas + +This katas is inspired and uses starting code +from [this great github repository](https://github.com/emilybache/Tennis-Refactoring-Kata) from Emily Bache + +## Tennis Coding Kata + +Tennis has a rather quirky scoring system, and to newcomers it can be a little difficult to keep track of. The tennis +society has contracted you to build a scoreboard to display the current score during tennis games. + +Your task is to write a “TennisGame†class containing the logic which outputs the correct score as a string for display +on the scoreboard. When a player scores a point, it triggers a method to be called on your class letting you know who +scored the point. Later, you will get a call “score()†from the scoreboard asking what it should display. This method +should return a string with the current score. + +You can read more about Tennis scores [here](http://en.wikipedia.org/wiki/Tennis#Scoring) which is summarized below: + +1. A game is won by the first player to have scored at least four points in total and at least two points more than the + opponent. +2. The running score of each game is described in a manner peculiar to tennis: scores from zero to three points are + described as "Love", "Fifteen", "Thirty", and "Forty" respectively. +3. If at least three points have been scored by each player, and the scores are equal, the score is "Deuce". +4. If at least three points have been scored by each side and a player has one more point than his opponent, the score + of the game is "Advantage" for the player in the lead. + +You need only report the score for the current game. Sets and Matches are out of scope. + +## Tennis Refactoring Kata + +Imagine you work for a consultancy company, and one of your colleagues has been doing some work for the Tennis Society. +The contract is for 10 hours billable work, and your colleague has spent 8.5 hours working on it. Unfortunately he has +now fallen ill. He says he has completed the work, and the tests all pass. Your boss has asked you to take over from +him. She wants you to spend an hour or so on the code so she can bill the client for the full 10 hours. She instructs +you to tidy up the code a little and perhaps make some notes so you can give your colleague some feedback on his chosen +design. You should also prepare to talk to your boss about the value of this refactoring work, over and above the extra +billable hours. + +There are three versions of this refactoring kata, each with their own design smells and challenges. I suggest you start +with the first one, with the class "TennisGame1". The test suite provided is fairly comprehensive, and fast to run. You +should not need to change the tests, only run them often as you refactor. + +If you like this Kata, you may be interested in my +book, ["The Coding Dojo Handbook"](https://leanpub.com/codingdojohandbook) + +## Questions to discuss afterwards + +* How did it feel to work with such fast, comprehensive tests? +* Did you make mistakes while refactoring that were caught by the tests? +* If you used a tool to record your test runs, review it. Could you have taken smaller steps? Made fewer refactoring + mistakes? +* Did you ever make any refactoring mistakes and then back out your changes? How did it feel to throw away code? +* What would you say to your colleague if they had written this code? +* What would you say to your boss about the value of this refactoring work? Was there more reason to do it over and + above the extra billable hour or so? diff --git a/tennis/refactoring/.gitignore b/tennis/refactoring/.gitignore new file mode 100644 index 00000000..f516d5e8 --- /dev/null +++ b/tennis/refactoring/.gitignore @@ -0,0 +1,6 @@ +.idea +*.iml +target/ +.classpath +.project +.settings/ diff --git a/tennis/refactoring/.gitlab-ci.yml b/tennis/refactoring/.gitlab-ci.yml new file mode 100644 index 00000000..03ffc524 --- /dev/null +++ b/tennis/refactoring/.gitlab-ci.yml @@ -0,0 +1,11 @@ +package-tennis-refactoring: + variables: + PROJECT_FOLDER: "tennis/refactoring" + extends: .java + only: + refs: + - master + - merge_requests + changes: + - ".gitlab-common-ci.yml" + - "tennis/refactoring/**/*" diff --git a/tennis/refactoring/pom.xml b/tennis/refactoring/pom.xml new file mode 100644 index 00000000..4d593d31 --- /dev/null +++ b/tennis/refactoring/pom.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <version>1.0.0</version> + <groupId>fr.ippon.kata</groupId> + <artifactId>java-parent</artifactId> + <relativePath>../../java-parent</relativePath> + </parent> + + <version>1.0.0-SNAPSHOT</version> + <artifactId>kata-refactoring-tennis</artifactId> + + <developers> + <developer> + <email>msauboua@ippon.fr</email> + <name>Matthieu SAUBOUA-BENELUZ</name> + </developer> + <developer> + <email>arey@ippon.fr</email> + <name>Anthony REY</name> + </developer> + </developers> + + <properties> + </properties> + + <dependencies> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <version>5.7.0</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame.java b/tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame.java new file mode 100644 index 00000000..b61bd08e --- /dev/null +++ b/tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame.java @@ -0,0 +1,7 @@ +package fr.ippon.kata; + +public interface TennisGame { + void wonPoint(String playerName); + + String score(); +} diff --git a/tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame1.java b/tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame1.java new file mode 100644 index 00000000..a80e8cd1 --- /dev/null +++ b/tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame1.java @@ -0,0 +1,78 @@ +package fr.ippon.kata; + + +import java.util.Map; + +public class TennisGame1 implements TennisGame { + + private static final String LOVE = "Love"; + private static final String FIFTEEN = "Fifteen"; + private static final String THIRTY = "Thirty"; + private static final String FORTY = "Forty"; + private static final String ALL = "All"; + private static final String DEUCE = "Deuce"; + private static final String SCORE_SEPARATOR = "-"; + + private static final Map<Integer, String> SCORES = Map.of(0, LOVE, 1, FIFTEEN, 2, THIRTY, 3, FORTY); + private static final String WIN_FOR = "Win for "; + private static final String ADVANTAGE = "Advantage "; + + private int player1Score = 0; + private int player2Score = 0; + private final String player1Name; + private final String player2Name; + + public TennisGame1(String player1Name, String player2Name) { + this.player1Name = player1Name; + this.player2Name = player2Name; + } + + public void wonPoint(String playerName) { + if (player1Name.equals(playerName)) + player1Score += 1; + else + player2Score += 1; + } + + public String score() { + if (player1Score == player2Score) { + return equalityScore(); + } + + if (player1Score >= 4 || player2Score >= 4) { + return endGameScore(); + } + + return middleGameScore(); + } + + private String middleGameScore() { + return SCORES.get(player1Score) + + SCORE_SEPARATOR + + SCORES.get(player2Score); + } + + private String endGameScore() { + int player1AdvantageValue = player1Score - player2Score; + + if (Math.abs(player1AdvantageValue) == 1) { + return ADVANTAGE + advantagedPlayer(); + } + + return WIN_FOR + advantagedPlayer(); + } + + private String advantagedPlayer() { + return player1Score > player2Score ? player1Name : player2Name; + } + + private String equalityScore() { + if (player1Score >= 3) { + return DEUCE; + } + + return SCORES.get(player1Score) + + SCORE_SEPARATOR + + ALL; + } +} diff --git a/tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame2.java b/tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame2.java new file mode 100644 index 00000000..97605197 --- /dev/null +++ b/tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame2.java @@ -0,0 +1,125 @@ +package fr.ippon.kata; + + +public class TennisGame2 implements TennisGame { + public int P1point = 0; + public int P2point = 0; + + public String P1res = ""; + public String P2res = ""; + private String player1Name; + private String player2Name; + + public TennisGame2(String player1Name, String player2Name) { + this.player1Name = player1Name; + this.player2Name = player2Name; + } + + public String score() { + String score = ""; + if (P1point == P2point && P1point < 4) { + if (P1point == 0) + score = "Love"; + if (P1point == 1) + score = "Fifteen"; + if (P1point == 2) + score = "Thirty"; + score += "-All"; + } + if (P1point == P2point && P1point >= 3) + score = "Deuce"; + + if (P1point > 0 && P2point == 0) { + if (P1point == 1) + P1res = "Fifteen"; + if (P1point == 2) + P1res = "Thirty"; + if (P1point == 3) + P1res = "Forty"; + + P2res = "Love"; + score = P1res + "-" + P2res; + } + if (P2point > 0 && P1point == 0) { + if (P2point == 1) + P2res = "Fifteen"; + if (P2point == 2) + P2res = "Thirty"; + if (P2point == 3) + P2res = "Forty"; + + P1res = "Love"; + score = P1res + "-" + P2res; + } + + if (P1point > P2point && P1point < 4) { + if (P1point == 2) + P1res = "Thirty"; + if (P1point == 3) + P1res = "Forty"; + if (P2point == 1) + P2res = "Fifteen"; + if (P2point == 2) + P2res = "Thirty"; + score = P1res + "-" + P2res; + } + if (P2point > P1point && P2point < 4) { + if (P2point == 2) + P2res = "Thirty"; + if (P2point == 3) + P2res = "Forty"; + if (P1point == 1) + P1res = "Fifteen"; + if (P1point == 2) + P1res = "Thirty"; + score = P1res + "-" + P2res; + } + + if (P1point > P2point && P2point >= 3) { + score = "Advantage player1"; + } + + if (P2point > P1point && P1point >= 3) { + score = "Advantage player2"; + } + + if (P1point >= 4 && P2point >= 0 && (P1point - P2point) >= 2) { + score = "Win for player1"; + } + if (P2point >= 4 && P1point >= 0 && (P2point - P1point) >= 2) { + score = "Win for player2"; + } + return score; + } + + public void SetP1Score(int number) { + + for (int i = 0; i < number; i++) { + P1Score(); + } + + } + + public void SetP2Score(int number) { + + for (int i = 0; i < number; i++) { + P2Score(); + } + + } + + public void P1Score() { + P1point++; + } + + public void P2Score() { + P2point++; + } + + public void wonPoint(String player) { + if (player == "player1") + P1Score(); + else + P2Score(); + } +} diff --git a/tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame3.java b/tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame3.java new file mode 100644 index 00000000..0465a533 --- /dev/null +++ b/tennis/refactoring/src/main/java/fr/ippon/kata/TennisGame3.java @@ -0,0 +1,38 @@ +package fr.ippon.kata; + + +public class TennisGame3 implements TennisGame { + + private int p2; + private int p1; + private String p1N; + private String p2N; + + public TennisGame3(String p1N, String p2N) { + this.p1N = p1N; + this.p2N = p2N; + } + + public String score() { + String s; + if (p1 < 4 && p2 < 4 && !(p1 + p2 == 6)) { + String[] p = new String[]{"Love", "Fifteen", "Thirty", "Forty"}; + s = p[p1]; + return (p1 == p2) ? s + "-All" : s + "-" + p[p2]; + } else { + if (p1 == p2) + return "Deuce"; + s = p1 > p2 ? p1N : p2N; + return ((p1 - p2) * (p1 - p2) == 1) ? "Advantage " + s : "Win for " + s; + } + } + + public void wonPoint(String playerName) { + if (playerName == "player1") + this.p1 += 1; + else + this.p2 += 1; + + } + +} diff --git a/tennis/refactoring/src/test/java/fr/ippon/kata/TennisGameUnitTest.java b/tennis/refactoring/src/test/java/fr/ippon/kata/TennisGameUnitTest.java new file mode 100644 index 00000000..d9d425e1 --- /dev/null +++ b/tennis/refactoring/src/test/java/fr/ippon/kata/TennisGameUnitTest.java @@ -0,0 +1,64 @@ +package fr.ippon.kata; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvFileSource; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class TennisGameUnitTest { + + @ParameterizedTest + @CsvFileSource(delimiter = ',', numLinesToSkip = 1, resources = "/param-test-data.csv") + void checkAllScoresTennisGame1(Integer player1Score, Integer player2Score, String expectedScore) { + TennisGame1 game = new TennisGame1("player1", "player2"); + checkAllScores(player1Score, player2Score, expectedScore, game); + } + + @Test + void checkForRealPlayerNames() { + String benoit_paire = "Benoit Paire"; + String roger_federer = "Roger Federer"; + + TennisGame1 game = new TennisGame1(benoit_paire, roger_federer); + + game.wonPoint(benoit_paire); + game.wonPoint(benoit_paire); + game.wonPoint(benoit_paire); + game.wonPoint(roger_federer); + game.wonPoint(roger_federer); + game.wonPoint(roger_federer); + game.wonPoint(roger_federer); + game.wonPoint(benoit_paire); + game.wonPoint(benoit_paire); + + assertThat(game.score()).isEqualTo("Advantage Benoit Paire"); + } + + @ParameterizedTest + @CsvFileSource(delimiter = ',', numLinesToSkip = 1, resources = "/param-test-data.csv") + void checkAllScoresTennisGame2(Integer player1Score, Integer player2Score, String expectedScore) { + TennisGame2 game = new TennisGame2("player1", "player2"); + checkAllScores(player1Score, player2Score, expectedScore, game); + } + + @ParameterizedTest + @CsvFileSource(delimiter = ',', numLinesToSkip = 1, resources = "/param-test-data.csv") + void checkAllScoresTennisGame3(Integer player1Score, Integer player2Score, String expectedScore) { + TennisGame3 game = new TennisGame3("player1", "player2"); + checkAllScores(player1Score, player2Score, expectedScore, game); + } + + private void checkAllScores(Integer player1Score, Integer player2Score, String expectedScore, TennisGame game) { + int highestScore = Math.max(player1Score, player2Score); + + for (int i = 0; i < highestScore; i++) { + if (i < player1Score) + game.wonPoint("player1"); + if (i < player2Score) + game.wonPoint("player2"); + } + + assertThat(game.score()).isEqualTo(expectedScore); + } +} diff --git a/tennis/refactoring/src/test/resources/param-test-data.csv b/tennis/refactoring/src/test/resources/param-test-data.csv new file mode 100644 index 00000000..450fb23c --- /dev/null +++ b/tennis/refactoring/src/test/resources/param-test-data.csv @@ -0,0 +1,34 @@ +player1Score,player2Score,ExpectedResult +0,0,Love-All +1,1,Fifteen-All +2,2,Thirty-All +3,3,Deuce +4,4,Deuce +1,0,Fifteen-Love +0,1,Love-Fifteen +2,0,Thirty-Love +0,2,Love-Thirty +3,0,Forty-Love +0,3,Love-Forty +4,0,Win for player1 +0,4,Win for player2 +2,1,Thirty-Fifteen +1,2,Fifteen-Thirty +3,1,Forty-Fifteen +1,3,Fifteen-Forty +4,1,Win for player1 +1,4,Win for player2 +3,2,Forty-Thirty +2,3,Thirty-Forty +4,2,Win for player1 +2,4,Win for player2 +4,3,Advantage player1 +3,4,Advantage player2 +5,4,Advantage player1 +4,5,Advantage player2 +15,14,Advantage player1 +14,15,Advantage player2 +6,4,Win for player1 +4,6,Win for player2 +16,14,Win for player1 +14,16,Win for player2 -- GitLab