Skip to content

Commit

Permalink
Feature/tests (#3)
Browse files Browse the repository at this point in the history
* Fix checkstyle

* Add dependency to fopbot

* Custom world implementation

* Interface for creating problem solver

* Simple maze solver algorithm

* Visualizer for maze

* Add default constructors

* Json Junit Test Support

* Snapshot dependency

* Improve code namings

* numberOfSteps should also contain the direction as input

* Add color to start and end point

* Add support for serialization

* Utilities for adding criterions and reflection stuff

* Reflection stuff

* H1 | DirectionVector Tests

* H2 | World Tests

* H3 | MazeSolverRecursive Tests

* H4 | MazeSolverIterative Tests

* Finialize rubrics

* Remove obsolete test cases

* documentation

* Remove obsolete code

* Change x to dx

* Remove obsolete comments

* Remove obsolete comments

* Remove obsolete code

* Remove obsolete code

* Remove helper function since they have the same signature as the main function

* Add requirements check

* Fix nextStep input must be not the rotated vector. The rotation will be checked in the method (#4)

* Add dependency to fopbot

* Custom world implementation

* Simple maze solver algorithm

* Improve code namings

* Add color to start and end point

* Fix failing tests due to missing exclusion

* Bump tudalgo fixing spoon type error

---------

Co-authored-by: zentox <[email protected]>
  • Loading branch information
zentox and zentox authored Aug 28, 2023
1 parent 7703378 commit 68d9306
Show file tree
Hide file tree
Showing 58 changed files with 4,761 additions and 3 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies {
implementation(libs.annotations)
implementation(libs.algoutils.student)
testImplementation(libs.bundles.junit)
testImplementation(libs.junit.core)
implementation(libs.fopbot)
}

Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[versions]
algoutils = "0.6.3"
algoutils = "0.7.0-SNAPSHOT"

[plugins]
style = { id = "org.sourcegrade.style", version = "2.1.0" }
Expand Down
16 changes: 16 additions & 0 deletions src/graderPublic/java/h06/H06_RubricProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,26 @@
import org.sourcegrade.jagr.api.rubric.Rubric;
import org.sourcegrade.jagr.api.rubric.RubricProvider;

import static h06.TutorUtils.criterion;

/**
* Defines the rubrics for H06.
*
* @author Nhan Huynh
*/
public class H06_RubricProvider implements RubricProvider {

/**
* The rubric for H06.
*/
public static final Rubric RUBRIC = Rubric.builder()
.title("H06")
.addChildCriteria(
criterion(H1_DirectionVectorTest.class),
criterion(H2_WorldTest.class),
criterion(H3_MazeSolverRecursiveTest.class),
criterion(H4_MazeSolverIterativeTest.class)
)
.build();

@Override
Expand Down
151 changes: 151 additions & 0 deletions src/graderPublic/java/h06/H1_DirectionVectorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package h06;

import h06.world.DirectionVector;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junitpioneer.jupiter.json.JsonClasspathSource;
import org.junitpioneer.jupiter.json.Property;
import org.sourcegrade.jagr.api.rubric.TestForSubmission;
import org.tudalgo.algoutils.tutor.general.assertions.Context;
import org.tudalgo.algoutils.tutor.general.reflections.BasicMethodLink;
import org.tudalgo.algoutils.tutor.general.reflections.MethodLink;
import org.tudalgo.algoutils.tutor.general.reflections.TypeLink;
import spoon.reflect.code.CtConditional;
import spoon.reflect.code.CtIf;
import spoon.reflect.code.CtReturn;
import spoon.reflect.declaration.CtElement;

import java.util.List;

import static h06.TutorUtils.getMethodLink;
import static h06.TutorUtils.getTypeLink;
import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.assertEquals;
import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.assertFalse;
import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.assertTrue;
import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.contextBuilder;

/**
* Defines unit tests for {@link DirectionVector}.
*
* @author Nhan Huynh
*/
@DisplayName("H1 | DirectionVector")
@TestForSubmission
public class H1_DirectionVectorTest {

/**
* Defines unit tests for the method {@link DirectionVector#rotate270()}.
*/
@DisplayName("H1.1 | rotate270()")
@Nested
public class Rotate270Test {

/**
* Tests whether the input vector is rotated correctly.
*
* @param input the input vector
* @param expected the expected vector
*/
@ParameterizedTest(name = "Input: {0}")
@DisplayName("01 | Methode rotate270() gibt in allen Fällen den korrekten Vektor zurück.")
@JsonClasspathSource(value = {
"DirectionVector/rotate270/up.json",
"DirectionVector/rotate270/right.json",
"DirectionVector/rotate270/down.json",
"DirectionVector/rotate270/left.json",
})
public void testRotate270(
@Property("input") DirectionVector input,
@Property("expected") DirectionVector expected
) {
TypeLink type = getTypeLink(Package.WORLD, DirectionVector.class);
MethodLink method = getMethodLink(Package.WORLD, DirectionVector.class, "rotate270");
DirectionVector actual = input.rotate270();
Context context = contextBuilder().subject(method)
.add("Input", input)
.add("Expected", expected)
.add("Actual", actual)
.build();
assertEquals(
expected, actual, context,
result -> "DirectionVector#rotate270() should return %s, but was %s.".formatted(expected, result)
);
}

@DisplayName("02 | Verbindliche Anforderungen")
@Test
public void testRequirements() {
TypeLink type = getTypeLink(Package.WORLD, DirectionVector.class);
BasicMethodLink method = ((BasicMethodLink) getMethodLink(Package.WORLD, DirectionVector.class, "rotate270"));
Context context = contextBuilder().subject(method).build();

List<CtReturn<?>> returns = method.getCtElement().filterChildren(it -> it instanceof CtReturn<?>)
.list();
boolean found = false;
for (CtReturn<?> ret : returns) {
if (ret.getReturnedExpression() instanceof CtConditional<?> expression) {
found = true;
break;
}
}
assertTrue(found, context,
result -> "DirectionVector#rotate270() should contain exactly one conditional statement, but found %s"
.formatted(returns.stream().map(CtReturn::getReturnedExpression).toList()));
}
}

/**
* Defines unit tests for the method {@link DirectionVector#rotate90()}.
*/
@DisplayName("H1.2 | rotate90()")
@Nested
public class Rotate90Test {

/**
* Tests whether the input vector is rotated correctly.
*
* @param input the input vector
* @param expected the expected vector
*/
@ParameterizedTest(name = "Input: {0}")
@DisplayName("03 | Methode rotate90() gibt in allen Fällen den korrekten Vektor zurück.")
@JsonClasspathSource(value = {
"DirectionVector/rotate90/up.json",
"DirectionVector/rotate90/right.json",
"DirectionVector/rotate90/down.json",
"DirectionVector/rotate90/left.json",
})
public void testRotate90(
@Property("input") DirectionVector input,
@Property("expected") DirectionVector expected
) {
MethodLink method = getMethodLink(Package.WORLD, DirectionVector.class, "rotate90");
DirectionVector actual = input.rotate90();
Context context = contextBuilder().subject(method)
.add("Input", input)
.add("Expected", expected)
.add("Actual", actual)
.build();
assertEquals(
expected, actual, context,
result -> "DirectionVector#rotate90() should return %s, but was %s.".formatted(expected, actual)
);
}

@DisplayName("04 | Verbindliche Anforderungen")
@Test
public void testRequirements() {
TypeLink type = getTypeLink(Package.WORLD, DirectionVector.class);
BasicMethodLink method = ((BasicMethodLink) getMethodLink(Package.WORLD, DirectionVector.class, "rotate90"));
Context context = contextBuilder().subject(method).build();

List<CtElement> elements = method.getCtElement().filterChildren(it -> it instanceof CtIf).list();

assertFalse(elements.isEmpty(), context,
result -> "DirectionVector#rotate90() should contain exactly one if-else statement, but found %s"
.formatted(elements));
}
}
}
198 changes: 198 additions & 0 deletions src/graderPublic/java/h06/H2_WorldTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package h06;

import h06.world.DirectionVector;
import h06.world.World;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junitpioneer.jupiter.json.JsonClasspathSource;
import org.junitpioneer.jupiter.json.Property;
import org.sourcegrade.jagr.api.rubric.TestForSubmission;
import org.tudalgo.algoutils.tutor.general.assertions.Context;
import org.tudalgo.algoutils.tutor.general.match.BasicReflectionMatchers;
import org.tudalgo.algoutils.tutor.general.reflections.BasicTypeLink;
import org.tudalgo.algoutils.tutor.general.reflections.MethodLink;

import java.awt.Point;

import static h06.TutorUtils.buildWorldContext;
import static h06.TutorUtils.getMethodLink;
import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.assertEquals;
import static org.tudalgo.algoutils.tutor.general.assertions.Assertions2.contextBuilder;

/**
* Defines unit tests for {@link World}.
*
* @see H1_DirectionVectorTest
*/
@DisplayName("H2 | World")
@TestForSubmission
public class H2_WorldTest {

/**
* Returns the method {@link World#isBlocked(Point, DirectionVector)} used for context information.
*
* @return the method {@link World#isBlocked(Point, DirectionVector)} used for context information
*/
private MethodLink getMethod() {
return getMethodLink(
Package.WORLD,
World.class,
"isBlocked",
BasicReflectionMatchers.sameTypes(BasicTypeLink.of(Point.class), BasicTypeLink.of(DirectionVector.class))
);
}

/**
* Tests whether {@link World#isBlocked(Point, DirectionVector)} returns the expected value.
*
* @param properties the properties of the world
* @param p the point to test
* @param d the direction vector to test
* @param expected the expected value
*/
private void assertIsBlocked(WorldProperties properties, Point p, DirectionVector d, boolean expected) {
MethodLink method = getMethod();

World world = properties.createWorld();
boolean actual = world.isBlocked(p, d);

Context context = contextBuilder().subject(method)
.add(buildWorldContext(properties))
.build();

assertEquals(
expected, actual, context,
result -> "World#isBlocked(%s, %s) should return %s, but was %s.".formatted(p, d, expected, result)
);
}


/**
* Tests whether {@link World#isBlocked(Point, DirectionVector)} returns the correct value for points outside
* the world.
*
* @param properties the properties of the world
* @param p the point to test
* @param d the direction vector to test
*/
@ParameterizedTest(name = "Koordinate: {1}, Richtung: {2}")
@DisplayName("05 | isBlocked(Point, DirectionVector) gibt für Koordinaten außerhalb der Welt false true.")
@JsonClasspathSource(value = {
"World/isBlocked/outside_1.json",
"World/isBlocked/outside_2.json",
"World/isBlocked/outside_3.json",
"World/isBlocked/outside_4.json",
"World/isBlocked/outside_5.json",
})
public void testIsBlockedOutside(
@Property("properties") WorldProperties properties,
@Property("p") Point p,
@Property("d") DirectionVector d) {
assertIsBlocked(properties, p, d, true);
}

/**
* Tests whether {@link World#isBlocked(Point, DirectionVector)} returns the correct value for points inside
* the world. (Only direction left and up).
*
* @param properties the properties of the world
* @param p the point to test
* @param d the direction vector to test
* @param expected the expected value
*/
@ParameterizedTest(name = "Koordinate: {1}, Richtung: {2}")
@DisplayName("06 | isBlocked(Point, DirectionVector) gibt für Koordinaten innerhalb der Welt mit "
+ "Richtungsvektor links und oben korrekte Werte zurück.")
@JsonClasspathSource(value = {
"World/isBlocked/leftup_1.json",
"World/isBlocked/leftup_2.json",
"World/isBlocked/leftup_3.json",
"World/isBlocked/leftup_4.json",
"World/isBlocked/leftup_5.json",
})
public void testIsBlockedRightAndDown(
@Property("properties") WorldProperties properties,
@Property("p") Point p,
@Property("d") DirectionVector d,
@Property("expected") boolean expected) {
assertIsBlocked(properties, p, d, expected);
}

/**
* Tests whether {@link World#isBlocked(Point, DirectionVector)} returns the correct value for points inside
* the world. (Only direction right and down).
*
* @param properties the properties of the world
* @param p the point to test
* @param d the direction vector to test
* @param expected the expected value
*/
@ParameterizedTest(name = "Koordinate: {1}, Richtung: {2}")
@DisplayName("07 | isBlocked(Point, DirectionVector) gibt für Koordinaten innerhalb der Welt mit "
+ "Richtungsvektor rechts und unten korrekte Werte zurück.")
@JsonClasspathSource(value = {
"World/isBlocked/rightdown_1.json",
"World/isBlocked/rightdown_2.json",
"World/isBlocked/rightdown_3.json",
"World/isBlocked/rightdown_4.json",
})
public void testIsBlockedLeftAndUp(
@Property("properties") WorldProperties properties,
@Property("p") Point p,
@Property("d") DirectionVector d,
@Property("expected") boolean expected) {
assertIsBlocked(properties, p, d, expected);
}

@ParameterizedTest(name = "Koordinate: {1}, Richtung: {2}")
@DisplayName("08 | isBlocked(Point, DirectionVector) ruft die Methode isBlocked(int, int, boolean) mit der "
+ "richtigen Wand Orientierung auf.")
@JsonClasspathSource(value = {
"World/isBlocked/orientation_left.json",
"World/isBlocked/orientation_right.json",
"World/isBlocked/orientation_up.json",
"World/isBlocked/orientation_down.json",
})
public void testOrientation(
@Property("properties") WorldProperties properties,
@Property("p") Point p,
@Property("d") DirectionVector d,
@Property("expected") boolean expected) {
MethodLink method = getMethod();

World world = properties.createWorld();
boolean actual = world.isBlocked(p, d);

Context context = contextBuilder().subject(method)
.add(buildWorldContext(properties))
.build();

assertEquals(
expected, actual, context,
result -> "World#isBlocked(%s, %s) should call World#isBlocked(int, int, boolean) with".formatted(p, d)
+ "the orientation %s, but was %s.".formatted(expected, actual)
);
}

/**
* Tests whether {@link World#isBlocked(Point, DirectionVector)} calls the method
* {@link World#isBlocked(int, int, boolean)} with the correct wall orientation.
*/
private static class OrientationWorld extends World {

/**
* Creates a new world with the given width and height.
*
* @param width the width of the world
* @param height the height of the world
*/
public OrientationWorld(int width, int height) {
super(width, height);
}

@Override
public boolean isBlocked(int x, int y, boolean horizontal) {
return horizontal;
}
}
}
Loading

0 comments on commit 68d9306

Please sign in to comment.