question 1.
part a
Prompt the models, and return the response and prompt
The prompt:
<context> Suppose you are developing a game. This action-adventure game involves exploration in a post-apocalyptic version of McMaster University, where up to eight players may be accosted by zombies, mutated professors, angry Deans, killer squirrels, and more. Gameplay can be both competitive and cooperative. The game is to be released on the PS5, Xbox Series X, Nintendo Switch and, for some reason known only to your designers, the Sega Genesis, which is 16-bit only and hasn’t been manufactured since 1997. </context>
Thinking step by step, specify three types of tests for such a game.
Response:
- Cross-Platform Compatibility and Performance Testing
- Objective: Ensure consistent functionality and performance across all platforms (PS5, Xbox Series X, Nintendo Switch, Sega Genesis).
- Focus Areas:
- Sega Genesis Limitations: Validate that core gameplay mechanics (e.g., enemy spawns, physics) function within 16-bit hardware constraints (limited RAM, slower CPU).
- Visual/Performance Parity: Test graphical fidelity, frame rates, and load times to ensure modern consoles meet expectations while Genesis maintains playable performance.
- Input/Controller Support: Verify compatibility with platform-specific controllers (e.g., Genesis’ 3-button controller vs. modern analogs).
- Multiplayer Synchronization and Network Stress Testing
- Objective: Guarantee seamless interaction for up to 8 players in competitive/cooperative modes.
- Focus Areas:
- Latency and Desynchronization: Test scenarios like sudden player disconnects, zombie horde attacks, or environmental hazards to ensure all clients stay synchronized.
- Scalability: Stress-test servers with maximum players and complex actions (e.g., simultaneous boss fights) to identify lag or crashes.
- Mode-Specific Issues: Validate competitive mechanics (e.g., PvP balance) and cooperative objectives (e.g., shared puzzles) under varying network conditions.
- AI Behavior and Difficulty Balancing Testing
- Objective: Ensure enemies exhibit intelligent, glitch-free behavior and scale appropriately with player count.
- Focus Areas:
- Enemy-Specific Logic: Test unique AI patterns (e.g., “angry Deans” targeting leadership roles, “killer squirrels” using terrain agility).
- Pathfinding and Collision: Verify enemies navigate post-apocalyptic campus layouts (e.g., broken buildings) without clipping or getting stuck.
- Dynamic Difficulty: Assess if enemy spawn rates, health, and aggression adapt to player numbers (e.g., 8 players trigger larger hordes) without unfair spikes.
b
State two things that you think are poor, incorrect, or unclear about the answer
- The cross-platform compatibility testing section is vague. It is unclear whether emulation is acceptable or if testing must occur on the original hardware, which raises legal/technical hurdles
- Multiple player stress test assumesa that all devices have the ability connect to the internet, which the original Genesis might not have native support, and local multiple was limited to 2-4 players (against the target 8-player online feature)
c
State two things you think are good, valid, and helpful
- The answer prioritize Sega Gensis limitation, where it emphasizes on validation of core mechanics, and forces developers to confront challenges such as limited RAM, CPU, and outdated controllers.
- The multiple stress tests recommendations address common pain points in online games, such as desynchronization and lag, and focus on mode-specific issues.
d
State three tests for the game beyond aforementioned examples
- Error Recovery Testing: Systematically test how the game recovers from common failures (power loss, cartridge removal, memory card errors) across all platforms, with special focus on save game corruption prevention on the Genesis’s battery backup system.
- Memory Boundary Testing: Create test scenarios that deliberately push against the Genesis’s 64KB RAM limit by spawning maximum enemies and players while rapidly changing areas, using coverage metrics to verify memory management.
- Platform-Specific Input Latency Testing: Measure and compare input-to-action delay across all platforms during high-intensity scenes, with particular attention to the Genesis’s polling rate limitations and modern platforms’ wireless controller latency.
Reasoning: They specifically address the unique challenge of developing for both modern and legacy hardware. The tests are more precisely defined with specific coverage criteria and measurable outcomes
question 2.
Given the following four programs, each containing fault (defects). Each also includes test inputs that will result in a failure.
Answer the following questions about each program. (The mark allocation below is specified per program; that is, your answers are worth 9 marks per program for a total of 36 marks for this question. Be sure to remember the definitions of failure, fault and error given in lecture.)
/**
* Find last index of element
*
* @param x array to search
* @param y value to look for
* @return last index of y in x; -1 if absent
* @throws NullPointerException if x is null
*/
public static int findLast (int[] x, int y) {
for (int i=x.length-1; i > 0; i--) {
if (x[i] == y) {
return i;
}
}
return -1;
}
// test: x=[2, 3, 5]; y=2; Expected = 0
-
The fault is in the loop condition. The loop starts at
i = x.length - 1
and runs whilei > 0
. Therefore, the first element at index 0 will never checked. The fix is to change the loop condition toi >= 0
-
A test case that does not execute the fault would be
x=[5,2], y=2
. In this case, it will check for index 1, (value 2), and return the correct index. The fault (skipping index 0) is not executed. -
A case that executing fault without error state would be
x=[1,2], y=3
In this case, the loop skip index 0 (fault executed), but since the element is not in the array, it would result in -1. The error state does not lead to failure here. -
The case that execute and error state without failure would be
x=[2,5,3], y=5
In this case, the loop skip index 0 (error state), but the element is found at index 1, the correct result is return, so no failure, but runs into error state. -
For
x=[2,3,5], y=2
:- the loop checks indices 2, and 1, then exists when
i=0
(due toi>0
). - The first error state occurs when the loop exists without checking index 0, leading to incorrect return of -1 instead of 0. The program counter is at
return -1
, with the error state being the omission of checking index 0
- the loop checks indices 2, and 1, then exists when
/**
* Finds last index of zero
*
* @param x array to search
*
* @return last index of 0 in x; -1 if there is no zero
* @throws NullPointerException if x is null
*/
public static int lastZero (int[] x) {
for (int i = 0; i < x.length; i++) {
if (x[i] == 0) {
return i;
}
}
return -1;
}
// test: x=[0,1,0]; Expected = 2
-
The fault is that the program will return the first zero it saw, not the last one. The corrected code would be to change the loop condition to check from left-to-right:
for (int i = x.length - 1; i>=0; i--) { ... }
-
A test case that doesn’t execute the fault would be
x=[1,2,3]
, since there are no zeros in the array, returning -1 -
A test case that executes the fault but doesn’t cause an error state would be
x=[0]
. Since there are single zeros, it would return correct index, not no error -
A test case causes error state without failure is not possible, given that it will always exits when it found the first zero in the loop, leading to failure
-
First error State in given test case
- For
x=[0,1,0]
, the loop check index 0 and immediately returns 0 - The first error state occurs when the method returns prematurely, ignoring last zero at index 2
- For
/**
* Counts positive elements in array
*
* @param x array to search
* @return number of positive elements in x
* @throws NullPointerException if x is null
*/
public static int countPositive (int[] x) {
int count = 0;
for (int i=0; i < x.length; i++) {
if (x[i] >= 0) {
count++;
}
}
return count;
}
// test: x=[-4, 2, 0, 2] // Expected = 2
-
The fault is that the program will count zero as positive. It should only count positive elements. The corrected code to change the if condition:
if (x[i] > 0) { ... }
-
A test case that doesn’t execute the fault would be
x=[-1,-2,-3]
, since there are no positive numbers in the array, returning 0 -
A test case that executes the fault but doesn’t cause an error state is
x=[1,2]
, gtiven that the check forx[i] >= 0
(fault executed). Since all elements are positive, the count matches the expected result here. -
A test case causing error state without failure is not possible, given that every error state directly leads to an incorrect final count (failure).
-
For
x=[-4,2,0,2]
:- The first error state occurs at iteration
i=2
(element0
). Thecount
will be increment to2
given that0>=0
is true
- The first error state occurs at iteration
/**
* Count odd or positive elements in an array
*
* @param x array to search
* @return count of odd or positive elements in x
* @throws NullPointerException if x is null
*/
public static int oddOrPos (int[] x) {
// Effects: if x is null throw NullPointerException
// else return the number of elements in x that
// are either odd or positive (or both)
int count = 0;
for (int i = 0; i < x.length; i++) {
if (x[i]%2 == 1 || x[i] > 0) {
count++;
}
}
return count;
}
// test: x=[-3, -2, 0, 1, 4] // Expected = 3
-
The fault is that the condition
x[i]%2 == 1
doesn’t identify negative odd number. In Java, the operator%
will returns negative values for negative number. The proposed fix is to check for!= 0
:if (x[i] %2 != 0 || x[i] > 0) { ... }
-
A test case that doesn’t execute the fault would be
x=[2,4,6]
Since all of the number are positive, the modulo won’t get checked, meaning the fault won’t get executed -
A test case that execute the fault but does not result in an error state would be
x=[1,3,5]
. The fault is executed here, but the result is correct given that it is all positive odd numbers. -
A test case causing error state without failure is not possible, given that every error state directly leads to an incorrect final count (failing to count negative odd number). The program cannot recover from the error state.
-
For
x=[-3,-2, 0, 1, 4]
- The first error state occurs at
i=0
(element -3). The code evaulates-3 % 2 == 1
(false
here), and fails to count-3
as odd.
- The first error state occurs at
question 3.
Question
You are developing a simple calculator software, following test-driven principles. The next functionality you need to add is the
divide()
method with the usual semantics. Execute the main steps of TDD, in at least two iterations.a. Develop a reasonable test suite to start the development of the functionality. Define at least two unit tests.
b. Develop the functionality until it passes the tests.
c. Is the functionality complete? Do you notice anything missing? Hint: compare the functionality to the code you would have written in a non- TDD way. Elaborate on this in detail.
d. Improve the test suite based on your previous answer.
e. Improve the functionality.
f. Would you use TDD? If yes: in what context? If no: why? What is the element you liked in TDD and what is the element you find the hardest to manage?
- First iteration of TDD
public class Calculator {
public double divide(double numerator, double denominator) {
throw new UnsupportedOperationException("Not implemented yet");
}
}
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class CalculatorTest {
private Calculator calculator;
@BeforeEach
public void setUp() {
calculator = new Calculator();
}
@Test
public void testDividePositiveNumbers() {
assertEquals(2, calculator.divide(6, 3));
}
@Test
public void testDivideByZero() {
assertThrows(ArithmeticException.class, () -> {
calculator.divide(6, 0);
});
}
}
- Basic functionality
public class Calculator {
public int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("Division by zero is not allowed.");
}
return a / b;
}
}
- The functionality is not complete. Given that it does not handle division involving negative numbers, division of zero by a non-zero number, and floating-point division.
In non-TDD approach, we might have considered these cases upfront, whereas with TDD it encourages incremental development and testing.
- Improvement on test cases
@Test
public void testDivideNegativeNumbers() {
assertEquals(-2, calculator.divide(-6, 3));
assertEquals(2, calculator.divide(-6, -3));
}
@Test
public void testDivideZeroByNonZero() {
assertEquals(0, calculator.divide(0, 5));
}
@Test
public void testDivideNonIntegerResult() {
assertEquals(2, calculator.divide(5, 2)); // This test will fail
}
- Improvement on functionality to modify the return to double
public class Calculator {
public double divide(double a, double b) {
if (b == 0) {
throw new ArithmeticException("Division by zero is not allowed.");
}
return a / b;
}
}
with the following tests updated accordingly
@Test
public void testDividePositiveNumbers() {
assertEquals(2.0, calculator.divide(6.0, 3.0));
}
@Test
public void testDivideNegativeNumbers() {
assertEquals(-2.0, calculator.divide(-6.0, 3.0));
assertEquals(2.0, calculator.divide(-6.0, -3.0));
}
@Test
public void testDivideZeroByNonZero() {
assertEquals(0.0, calculator.divide(0.0, 5.0));
}
@Test
public void testDivideNonIntegerResult() {
assertEquals(2.5, calculator.divide(5.0, 2.0));
}
- Would you use TDD?
Yes, I would use TDD in contexts with well-defined requirements (e.g., API development or bug fixes), but No otherwise:
- Liked: Ensures test coverage.
- Hardest: Writing tests for complex interactions or unclear design, which can slow initial progress, add more initial technical debt.
For most real world tasks, it is impossible to well-defined requirements, given that requirements changes overtime, meaning the scope of the tests would have to change based on the requirements change. TDD would not fit with the fast-paced environment of a startup.