0% found this document useful (0 votes)
40 views

Notes On Style: Testing The Tictactoe Game

The document discusses testing strategies for a tic-tac-toe game. It describes an initial approach of creating test boards by initializing a 2D array. This is tedious to do for many test cases. An alternative method is presented that uses a single string input and a method to populate the board array. Refactoring existing test code is discussed to remove duplication and make the tests easier to understand and modify. Edge cases like invalid moves are also considered, along with ways to address them like adding exception handling to methods.

Uploaded by

PRADEEP KUMAR
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
40 views

Notes On Style: Testing The Tictactoe Game

The document discusses testing strategies for a tic-tac-toe game. It describes an initial approach of creating test boards by initializing a 2D array. This is tedious to do for many test cases. An alternative method is presented that uses a single string input and a method to populate the board array. Refactoring existing test code is discussed to remove duplication and make the tests easier to understand and modify. Edge cases like invalid moves are also considered, along with ways to address them like adding exception handling to methods.

Uploaded by

PRADEEP KUMAR
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
You are on page 1/ 18

Notes on Style

Testing the TicTacToe game

27-Apr-19
A first approach
 I want to create a large number of tic-tac-toe partial
games for testing purposes
 I could do it this way:
 char[][] array;
 array = new char[][] { { 'X', ' ', 'O' },
{ ' ', 'X', ' ' },
{ 'O', ' ', 'O' } };

 ...and I could reset the array for each tic-tac-toe board I


want to use as input
 This looks nice, and is easy to read, but it is a real
nuisance to type out a lot of these
So I did this instead
 setBoard(" o x o o");

 private void setBoard(String xxx) {


xxx = xxx.toUpperCase();
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
array[i][j] =
Character.toUpperCase(xxx.charAt(3 * i + j));
}
}
}

 Now it's a lot easier to create tic-tac-toe boards for testing


Morals
 Use methods to make your life easier
 If something is ugly, hide it in a method

 Also...
 While our main goal should be to write programs that
are easy to read, it isn’t our only goal
 The best thing to do with hard-to-read methods is to
rewrite them
 Second best is to explain them in comments
 I didn’t include the comments on the slide, but they are in
my code!
Refactoring
 Refactoring is reorganizing a program without changing
what it does
 Refactor in order to:
 Make a program easier to understand
 Make a program easier to modify
Before refactoring
 public final void testMakeCornerMove() {
setBoard(" oxoxxoxo");
computerPlayer.makeMove(board);
assertBoardIs("xoxoxxoxo");

setBoard("oo xxooxx");
computerPlayer.makeMove(board);
assertBoardIs("ooxxxooxx");

setBoard("oxoxxoxo ");
computerPlayer.makeMove(board);
assertBoardIs("oxoxxoxox");
}

 I seem to be doing the same thing over and over...


After refactoring
 private void beforeAndAfterMove(String before, String after) {
setBoard(before);
computerPlayer.makeMove(board);
assertBoardIs(after);
}
 public final void testMakeCornerMove() {
// Center and all other corners taken
beforeAndAfterMove(" o x o o", "x o x o o");
beforeAndAfterMove("o x o o", "o x x o o");
beforeAndAfterMove("o o x o ", "o o x o x");
beforeAndAfterMove("o o x o", "o o x x o");
// Corner move is all that's left
beforeAndAfterMove(" oxoxxoxo", "xoxoxxoxo");
beforeAndAfterMove("oo xxooxx", "ooxxxooxx");
beforeAndAfterMove("oxoxxoxo ", "oxoxxoxox");
beforeAndAfterMove("xxooxxxoo", "xxooxxxoo");
}
Moral
 The DRY principle: Don’t Repeat Yourself
 Every piece of data should have a single unique representation
 “A man with a watch knows what time it is. A man with two watches is
never sure.” -- Segal’s Law
 Example: If you have a measure of distance, don’t keep it in two
variables, distanceInFeet and distanceInMeters -- keep it in one
variable, and use a method to convert to the other units as needed
 Each nontrivial operation should be represented by a unique
piece of code
 Don’t “cut and paste” code--turn it into a method
 Variations in code can often be handled by a parameter list
 Corrections and updates are much simpler
Testing for a winning move
 Here’s one way to test for a winning move:
 if (board.get(1, 1) == 'X' && board.get(1, 2) == 'X'
&& board.get(1, 3) == ' ') {
board.set(1, 3, 'X');
return true;
}
 There are 24 combinations to test for
 This is why I made testing for a winning move optional!
 Using a method would help some
 if (winningMove(1, 1, 1, 2, 1, 3)) return true;
 But that’s still 24 error-prone lines, plus a method
A bright idea
 For each location on the tic-tac-toe board,
 Put an 'X' in that location
 Check for a win (with our computerHasWon() method)
 If it’s a win, that’s our move
 Otherwise, put a blank in that location, and keep trying

 We can do something very similar for testing if we need


to make a blocking move
The code
 private boolean makeWinningMove(TicTacToeBoard board) {
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (!board.isEmpty(i, j)) continue;
board.set(i, j, 'X');
if (board.computerHasWon()) return true;
board.set(i, j, ' ');
}
}
return false;
}
 This code works, but...
An unexpected consequence
 Row 1, column 1 is already taken.
Row 1, column 2 is already taken.
Row 2, column 1 is already taken.
Row 2, column 1 is already taken.
Row 2, column 3 is already taken.
Row 1, column 2 is already taken.
Row 2, column 1 is already taken.
Row 1, column 2 is already taken.
Row 2, column 1 is already taken.
Row 2, column 3 is already taken.
Row 3, column 2 is already taken.

 Why did this happen?


 I did check for a blank space before placing my 'X'
In the set method of TicTacToeBoard
 if (board[row - 1][column - 1] != ' ') {
error("Row " + row + ", column " + column +
" is already taken.");
}

 I can only “set” a location if it is initially blank


 I never thought about “erasing” an X or an O
 Proposed solution: Modify the set() method
 Problem: I asked you not to modify the provided methods
 Under these constraints, my “bright idea” cannot be
made to work :-(
Morals
 Insofar as possible, methods should do a single thing
 In particular, it’s usually a bad idea to mix computation
and input/output in the same method
 If you mix computation and input/output in the same method,
then you can’t do the computation without also doing the
input/output
 Example: In a previous assignment I specified methods
findDayOfWeek to only do computation, and
findAndPrintDayOfWeek to call the former and print the
results
 This allowed me to test your computations without getting a bunch of
output
Fix #1 for board.set(row, column, ch)
 I could have made set return a boolean--true if the location was
set, false if it wasn’t

 boolean set(int row, int column, char ch) {


if (board[row - 1][column - 1] == ' ' &&
(ch == 'X' || ch == 'O')) {
board[row – 1][column – 1] = ch;
return true;
}
else return false;
}

 Disadvantage: The user might not check the result


 Disadvantage: I test for two things that could go wrong (location
is taken, bad character) and this doesn’t distinguish between them
Fix#2 for board.set(row, column, ch)
 I could assert that the location is available, and assert
that the character is legal

 void set(int row, int column, char ch) {


assert board[row - 1][column - 1] == ' ';
assert ch == 'X' || 'O' ;
board[row – 1][column – 1] = ch;
}

 Disadvantage: Bad use of assert--it should be used for


things you believe to be true, not for error checking
Fix#3 and #4
 I could throw an Exception for each error condition
 This is the best solution
 We haven't covered Exceptions yet

 (I nearly forgot this one) I could just skip error checking


 Big disadvantage: No warning to the user if something is
wrong
The End

You might also like