18
Jul 17, 2 022 Notes on Style Testing the TicTacToe game

9-May-15 Notes on Style Testing the TicTacToe game

Embed Size (px)

Citation preview

Page 1: 9-May-15 Notes on Style Testing the TicTacToe game

Apr 18, 2023

Notes on Style

Testing the TicTacToe game

Page 2: 9-May-15 Notes on Style Testing the TicTacToe game

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

Page 3: 9-May-15 Notes on Style Testing the TicTacToe game

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

Page 4: 9-May-15 Notes on Style Testing the TicTacToe game

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!

Page 5: 9-May-15 Notes on Style Testing the TicTacToe game

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

Page 6: 9-May-15 Notes on Style Testing the TicTacToe game

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...

Page 7: 9-May-15 Notes on Style Testing the TicTacToe game

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");}

Page 8: 9-May-15 Notes on Style Testing the TicTacToe game

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

Page 9: 9-May-15 Notes on Style Testing the TicTacToe game

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

Page 10: 9-May-15 Notes on Style Testing the TicTacToe game

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

Page 11: 9-May-15 Notes on Style Testing the TicTacToe game

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...

Page 12: 9-May-15 Notes on Style Testing the TicTacToe game

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'

Page 13: 9-May-15 Notes on Style Testing the TicTacToe game

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 :-(

Page 14: 9-May-15 Notes on Style Testing the TicTacToe game

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

Page 15: 9-May-15 Notes on Style Testing the TicTacToe game

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

Page 16: 9-May-15 Notes on Style Testing the TicTacToe game

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

Page 17: 9-May-15 Notes on Style Testing the TicTacToe game

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

Page 18: 9-May-15 Notes on Style Testing the TicTacToe game

The End