You are encouraged to work through the TDD in process in your favorite IDE. Learning TDD is best accomplished by doing TDD.
Require: Java 8 (or higher), JUnit (you are recommended to use the latest version, JUnit 5).
Additional resources:
You may find class discussions on RIPR model,
test automation, and characteristics of good tests
helpful when designing and automating your tests.
You may work alone or with 1-2 students in this course (max team size = 3).
You will have 30 minutes. To facilitate your conversation, you may want to delegate a timekeeper to ensure you get to all of the tasks.
To get started, use the two-breath rule, introduce yourself. Get to know everyone in your development and testing team.
Imagine you are developing a Java-based, non-GUI Tic-Tac-Toe (you may assume any board size; for example, a 3x3 board). (Activity based in part on Viktor Farcic and Alex Garcia, "Test-Driven Java Development")
Note: For each task, an example is provided. You may include the example as part of your development. You should come up with at least one (of your own) user story and follow the TDD process to fulfill your user story.
Please also note: The example requirements are simplified and incomplete. Feel free to add or modify the requirements as appropriate.
Although there is no specific requirement on the number of user stories / tasks / tests / iterations, I hope that you will try / do your best to get the most out of this activity. You are the main driver of your learning success. — The more you do and practice, the more skill and understanding you'd get.
Work with your team. Think about a few main functionalities of a Tic-Tac-Toe software. Write one user story for each functionality.
Example:Pick at least one of your user stories. Design tests for the story (or stories). "Design" means to describe the inputs and expected outputs, resulting in concrete / executable / example-like tests.
Example:| Software requirements | Test requirements | Test cases |
|---|---|---|
| A piece can be placed on any empty space of a 3x3 board |
Follow the TDD process. Write one test, modify the software to make it work, refactor as needed. Repeat the process with new tests until the user story (stories) is (are) fulfilled. Use test doubles as needed.
Example:You will use tests as guidelines to design and develop a Tic-Tac-Toe software.
| Test file (TicTacToe_Test.java) | Java class / SUT (TicTacToe.java) |
|---|---|
1. Create a JUnit test file
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Assertions;
public class TicTacToe_Test
{
}
Note: This activity (example) use JUnit 5. For JUnit 4 developers/testers, be sure to import appropriate classes instead of the above import static org.junit.Assert.*; import org.junit.After; import org.junit.Before; import org.junit.Test; |
|
2. Set up prefix
public class TicTacToe_Test
{
private TicTacToe ttt;
@BeforeEach
public void setUp() throws Exception
{
ttt = new TicTacToe();
}
}
Notice: the test file does not compile. This is because a TicTacToe class does not exist.
Note: For JUnit 4 developers/testers,
use |
|
3. Create a Java class to be developed with TDD
public class TicTacToe
{
}
Now, the test file can be compiled.
|
|
4. Create a test method to satisfy TR1
@Test
public void whenXOutsideBoardException()
{
Assertions.assertThrows(RuntimeException.class, () -> {
ttt.play(4, 2);
});
}
Notice: the test file does not compile since a play() method does not exist. Note: For JUnit 4 developers/testers, exception handling may be @Test (expected=RuntimeException.class)
public void whenXOutsideBoardException()
{
ttt.play(4, 2);
}
|
|
5. Create a play() method.
public void play(int x, int y)
{
}
Now, the test file can be compiled.
|
|
| 6. Run the test method.
The test fails because the play() method does not return the expecetd output. |
|
| 7. Add code to make the test pass
[Key idea: make the test pass as quickly as possible and with minimal effort] Note: As part of the TDD process, it is normal to start with a hard-coded throw (or return) statement to pass the test as quickly as possible and with minimal effort. Since the test expects a RunTimeException to be thrown, to make the test pass, the play() method can simply throw the exception. public void play(int x, int y)
{
throw new java.lang.RuntimeException("X is outside board");
}
Run the test method.
However, since this example is relatively small and straightforward, we can simply write code to check X and throw an exception. public void play(int x, int y)
{
if (x < 1 || x > 3)
throw new java.lang.RuntimeException("X is outside board");
}
|
|
| 8. Run the test method.
The test passes. |
|
9. Add test method to satisfy TR2
@Test
public void whenYOutsideBoardException()
{
Assertions.assertThrows(RuntimeException.class, () -> {
ttt.play(2, 4);
});
}
|
|
| 10. Run the test method.
The test fails because the play() method does not return the expecetd output. |
|
| 11. Add code to make the test pass
[Reminder -- key idea: make the test pass as quickly as possible and with minimal effort] public void play(int x, int y)
{
if (x < 1 || x > 3)
throw new java.lang.RuntimeException("X is outside board");
if (y < 1 || y > 3)
throw new java.lang.RuntimeException("Y is outside board");
}
|
|
| 12. Run the test method.
The test passes. |
|
13. Add test method to satisfy TR3
@Test
public void whenOccupiedException()
{
ttt.play(1, 3); // imitate player X places a piece in (1,3)
// imitate player O tries to place a piece in an occupied space (1,3)
Assertions.assertThrows(RuntimeException.class, () -> {
ttt.play(1, 3);
});
}
|
|
| 14. Run the test method.
The test fails because the play() method does not return the expecetd output. |
|
| 15. Add code to make the test pass
[Reminder -- key idea: make the test pass as quickly as possible and with minimal effort] To be able to identify if the space is occupied, the location of the placed pieces needs to be maintained. In this example, we will store the location of the placed pieces in an array. Every time a new piece is placed, we will verify that the place is not occupied. Otherwise, an exception will be thrown. public class TicTacToe
{
private String[][] board = { {"", "", ""},
{"", "", ""},
{"", "", ""} };
public void play(int x, int y)
{
if (x < 1 || x > 3)
throw new java.lang.RuntimeException("X is outside board");
if (y < 1 || y > 3)
throw new java.lang.RuntimeException("Y is outside board");
if (board[x-1][y-1] != "")
throw new java.lang.RuntimeException("Place is occupied");
else
board[x-1][y-1] = "occupied";
}
}
|
|
| 16. Run the test method.
The test passes. |
|
| Refactor the code
Refactoring is to improve the design and quality of the code. It is not rewriting the code nor changing the functionality of the code/program. [Reminder: test code is also code and should be refactored when possible] |
|
| 17. Refactor the code
[Key ideas: identify any potential refactoring and then decide which of them we will carry out] public void play(int x, int y)
{
checkAxis(x);
checkAxis(y);
setOccupied(x, y);
}
private void checkAxis(int axis)
{
if (axis < 1 || axis > 3)
throw new java.lang.RuntimeException("outside board");
}
private void setOccupied(int x, int y)
{
if (board[x-1][y-1] != "")
throw new java.lang.RuntimeException("Place is occupied");
else
board[x-1][y-1] = "occupied";
}
|
|
| 18. Run the test to ensure that refactoring does not break the SUT.
The test passes. |
|
Think about your experience with tasks 1-3 and answer the following questions.
CC-BY-NC-SA 4.0 license.