Add parameterized ShapeContractTest for Shape interface invariants
All checks were successful
CI / build-and-test (pull_request) Successful in 18s

Verify clone(), getBounds(), and translate() contracts across all Shape
implementations (SRectangle, SCircle, STriangle, SText, SCollection)
using @ParameterizedTest + @MethodSource.

Also adds junit-jupiter-params dependency to pom.xml.

Closes #8
This commit is contained in:
2026-03-27 16:13:59 +01:00
committed by Thibaud Gasser
parent c51ae8cfbb
commit 7ddef4d8ab
3 changed files with 105 additions and 0 deletions

View File

@@ -92,5 +92,12 @@
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,73 @@
package ovh.gasser.newshapes.shapes;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.awt.Rectangle;
import static org.junit.jupiter.api.Assertions.*;
/**
* Parameterized contract tests verifying Shape interface invariants
* across all implementations (SRectangle, SCircle, STriangle, SText, SCollection).
*/
class ShapeContractTest {
@ParameterizedTest(name = "{0}")
@MethodSource("ovh.gasser.newshapes.shapes.ShapeFactory#allShapes")
void cloneReturnsIndependentCopy(Shape shape) {
Shape cloned = shape.clone();
// clone must not be the same instance
assertNotSame(shape, cloned, "clone() must return a new instance");
// clone must have equal bounds
assertEquals(shape.getBounds(), cloned.getBounds(),
"clone() must preserve bounds");
// mutating the clone must not affect the original
Rectangle originalBounds = shape.getBounds();
cloned.translate(999, 999);
assertEquals(originalBounds, shape.getBounds(),
"Translating the clone must not affect the original's bounds");
}
@ParameterizedTest(name = "{0}")
@MethodSource("ovh.gasser.newshapes.shapes.ShapeFactory#allShapes")
void getBoundsReturnsCopy(Shape shape) {
Rectangle bounds1 = shape.getBounds();
Rectangle bounds2 = shape.getBounds();
// successive calls must return equal bounds
assertEquals(bounds1, bounds2,
"getBounds() must return consistent values");
// but not the same object (defensive copy)
assertNotSame(bounds1, bounds2,
"getBounds() must return a copy, not internal state");
// mutating the returned Rectangle must not affect the shape
bounds1.translate(500, 500);
assertEquals(bounds2, shape.getBounds(),
"Mutating the returned Rectangle must not affect the shape");
}
@ParameterizedTest(name = "{0}")
@MethodSource("ovh.gasser.newshapes.shapes.ShapeFactory#allShapes")
void translateMutatesInPlace(Shape shape) {
Rectangle before = shape.getBounds();
int dx = 7, dy = -3;
shape.translate(dx, dy);
Rectangle after = shape.getBounds();
assertEquals(before.x + dx, after.x,
"translate() must shift x by dx");
assertEquals(before.y + dy, after.y,
"translate() must shift y by dy");
assertEquals(before.width, after.width,
"translate() must not change width");
assertEquals(before.height, after.height,
"translate() must not change height");
}
}

View File

@@ -0,0 +1,25 @@
package ovh.gasser.newshapes.shapes;
import java.awt.Color;
import java.util.stream.Stream;
/**
* Provides Shape instances for parameterized contract tests.
*/
public final class ShapeFactory {
private ShapeFactory() {}
static Stream<Shape> allShapes() {
return Stream.of(
SRectangle.create(10, 20, 100, 50),
SCircle.create(5, 5, 30),
STriangle.create(0, 0, 40, Color.RED, Color.BLACK),
SText.create(15, 25, "Hello"),
SCollection.of(
SRectangle.create(0, 0, 20, 20),
SCircle.create(10, 10, 5)
)
);
}
}