By Henk Bierlee (4148053) and Kilian Grashoff (4171373)
###Exercise 16 State machine model for the state that is implicit in the requirements contained in doc/scenarios.md:
Transition tree derived from the state machine:
Note: we only made the tree for the Player part of our state machine, since the Ghost class can not be end-to-end tested properly (see our report for Part 1, exercise 4).
From this tree, we derived the following table by going from the root to all leaves:
Test Case ID | Start state | Events | End State |
---|---|---|---|
0 | Game suspended 0 | press start then press stop | Game suspended 1 |
1 | Game suspended 0 | press start then move [to empty Square] | Player at valid Square 1 |
2 | Game suspended 0 | press start then move [to Wall] | Player at valid Square 2 |
3 | Game suspended 0 | press start then move [to Pellet] | Player at valid Square 3 |
4 | Game suspended 0 | press start then move [to Ghost] | Game over |
5 | Game suspended 0 | press start then move [to last Pellet] | Game won |
STATES | events | ||||||
---|---|---|---|---|---|---|---|
press start | press stop | move [to empty Square] | move [to Wall] | move [to Pellet] | move [to Ghost] | move [to last Pellet] | |
Game suspended | Player at valid square | no action | no action | no action | no action | no action | no action |
Player at valid square | no action | Game suspended | Player at valid square | Player at valid square | Player at valid square | Game over | Game won |
Game over | not specified | no action | no action | no action | no action | no action | no action |
Game won | not specified | no action | no action | no action | no action | no action | no action |
####Exercise 19 All valid paths from the root to leaves of our transition tree are already tested by existing tests. Specifically (Test Case ID's can be found in exercise 17):
Case 0 is tested by SuspendGameTest.testSuspendGame()
Case 1 is tested by MovePlayerTest.testMoveToEmptySquare()
Case 2 is tested by MovePlayerTest.testMoveToWall()
Case 3 is tested by MovePlayerTest.testMoveToSquareWithPellet()
Case 4 is tested by MovePlayerTest.testPlayerDies()
Case 5 is tested by MovePlayerTest.testPlayerWins()
However, to show that we can apply the State Machine testing method, we have rewritten these tests in terms of states in the nl.tudelft.jpacman.group8.GameStateRoundTripTest
class. These tests use our SingleLevelLauncher
to load the small_map.txt
map.
The test cases for (state, event) pairs not contained in our diagram remain. These are the cases that have no action
or not specified
as result in the table. Not specified does not need to be tested (we do not care what the result is, as it is not defined in the requirements, so there is nothing to test). So we need to derive test cases for all no action
entries in the table. All entries corresponding to a move
event and one state (in the same row) can be compressed into one test case, since the move condition does not matter if there is no resulting action. This results in the following test cases, which can be found in the GameStateInvalidEventTest
:
Test Case ID | Start state | Events | Result |
---|---|---|---|
6 | Game suspended | press stop | no action |
7 | Game suspended | move | no action |
8 | Player at valid square | press start | no action |
9 | Game over | press stop | no action |
10 | Game over | move | no action |
11 | Game won | press stop | no action |
12 | Game won | move | no action |
####Exercise 20 User story S2.5 was adjusted (but ended up being implemented seperately for single and multi levels. See exercise 22):
Scenario S2.5: Player advances level, extends S2.1
When I have eaten the last pellet;
and I am not in the last level;
Then I advance to the next level.
User story S2.6 was added:
Scenario S2.6: Player wins, extends S2.1
When I have eaten the last pellet;
and I am in the last level;
Then I win the game.
The updated scenarios.md
file can be found in the doc folder of our project.
####Exercise 22 The updated state machine yielded two extra test cases:
Test Case ID | Start state | Events | Result |
---|---|---|---|
13 | Game suspended | move [to last Pellet], next level | Player at valid square |
14 | Game suspended | move [to last Pellet], win [level = lastLevel] | Game won |
The 'Eat last pellet' scenario (Test case ID: 5) appeared to be a likely candidate for test reuse. However, our implementation supports both single level and multi level games, and both are fully tested. The end result of that scenario is different for single level and multi level games, so we had to add tests instead of merely adjusting the existing single level testing.
####Exercise 23, 24 We decided on the following class hierarchy for this exercise:
We extended Launcher
to CustomLevelLauncher
, so we could load our own levels. Then, we made two subclasses of CustomLevelLauncher
: the SingleLevelLauncher
and the MultiLevelLauncher
. This way we can support both types of games. On the Game
side, we made the subclass MultiLevelGame
, which mirrors SinglePlayerGame
(from the framework), only with multiple level support.
(We initially considered to just extend SinglePlayerGame
- which might have made things simpler - but decided against it. Firstly, the assignment told us to extend Game
, not SinglePlayerGame
. Secondly, extending SinglePlayerGame
would result in the inheritance of obsolete fields in MultiLevelGame
, such as level
.)
####Exercise 25, 26 Our test suite accommodates the class structure of our extensions.
#####Structure
AbstractRoundTripTest
(innl.tudelft.jpacman.group8.gamestate.roundtrip
) contains tests cases which test behavior of anyGame
subclass (Test Case ID's: 0-4).SingleLevelRoundTripTest
contains test cases which test behavior specific toSinglePlayerGame
(Test Case ID: 5).MultiLevelRoundTripTest
contains test cases which test behavior specific toMultiLevelGame
(Test Case ID's: 13-14).
#####Implementation
AbstractRoundTripTest
forces subclasses to use their own type of Launcher
and Game
. Such a subclass has the responsibility to set the SubclassLauncher
and SubclassGame
fields in the setUp
method and return their values in the getGame
and getLauncher
methods. This way, the type that is to be tested is dynamically selected for each test.
####Exercise 27
The implementation of MultiLevelGame
was straightforward. Override getLevel
to return the current level according to a levelIndex
. Override gameWon
to increment the levelIndex
, or call stop
when there are no more levels.