Verified Commit a9ad70a5 authored by Daniel Mangold's avatar Daniel Mangold
Browse files

Version 1.0

parents 79e2654d fe3b78a1
1.0
src/test/java/h05/Utils.java afb4a4a21023642a53304ab9bbf2f6a9
src/test/java/h05/Assertions.java 02349c33cf60fea1b77b0c457bf1fad8
src/test/java/h05/ListItemTest.java d746f290bee1694c5b79e77538c86fa5
src/test/java/h05/MyTreeNodeTest.java d2de48e7fa2087a08fc6d81fdb273064
src/test/java/h05/MyTreeTest.java 5a77bf969645a08aa50966f4d5cb9319
src/test/java/h05/MyParenthesesTreeIteratorTest.java 311cd428d3c3c82069f28905bd5831e7
src/test/java/h05/provider/TreeProvider.java 955510b43e5a8b60e0d92aebbdf1b6a9
src/test/java/h05/provider/RandomTreeProvider.java 4f610933595546a166e106ad9929b290
\ No newline at end of file
# Community Tests für die fünfte Hausübung der AuD 2021
Zum Ausführen der Tests sollte eine [eigene JUnit Run Configuration](https://git.rwth-aachen.de/groups/aud-tests/-/wikis/JUnit-Run-Configuration) angelegt werden, da durch den gradle task nicht alle Meldungen angezeigt werden (z.B. warum Tests ignoriert werden).
Da einige Methoden intern nach bestimmten Anforderungen aufgebaut sein müssen, aber man mit Black-box testing (worunter diese Tests fallen) dabei nur bedingt Aussagen über die Korrektheit treffen kann, weil die Ergebnisse identisch sind, wird bei manchen Tests nur geprüft, ob die jeweiligen Definitionen und Rückgaben korrekt sind. Ob das bei einem bestimmten Test der Fall ist, steht in den Beschreibungen der jeweiligen Methoden.
Mit * markierte Methoden testen, ob die jeweilige Klasse bzw. Interface korrekt definiert ist. Sie sind an sich keine Testmethoden und nur in Verbindung mit "richtigen" Tests, also mit `@Test` oder vergleichbar annotierten Methoden, brauchbar.
Die Tests haben einen Update-Mechanismus / Installer, der vor jeder Ausführung nach Updates sucht. Da das einige Sekunden in Anspruch nehmen kann und vielleicht auch anderweitig nicht gewünscht ist, kann diese Funktionalität ausgeschaltet werden. Wird diese Funktionalität verwendet, muss das Arbeitsverzeichnis gleich dem Projektordner sein. Das Verhalten kann mit Änderung der folgenden Konstanten in [`Utils.java`](src/test/java/h05/Utils.java) verändert werden:
- `CHECK_FOR_UPDATES` <br>
Bestimmt, ob nach Updates gesucht wird. Ist diese Konstante `true`, dann wird bei jeder Ausführung der Tests nach Updates in diesem Repository gesucht und eine Meldung ausgegeben, sollten welche verfügbar sein. Ist sie `false` wird nicht nach Updates gesucht und andere Einstellungen werden ignoriert.
- `CHECK_HASHES` <br>
Gibt an, ob die MD5-Hashes der lokalen Tests mit denen im Repository abgeglichen werden sollen. Hat die Konstante den Wert `true`, dann werden die Hashes der Dateien in [`.test_version`](.test_version) mit den tatsächlichen verglichen und eine Meldung ausgegeben, sollten sie nicht übereinstimmen.
- `AUTO_UPDATE` <br>
Entscheidet, ob verfügbare Updates automatisch heruntergeladen werden sollen. Wenn aktiviert, werden Dateien, deren Hashes nicht übereinstimmen, erneut aus dem Repository heruntergeladen, wenn ein Update verfügbar ist. Diese Option ignoriert `CHECK_HASHES`, sollte die lokale Kopie der Tests veraltet sein. Da der Inhalt der Dateien überschrieben wird, werden lokale Änderungen an den Tests verloren gehen!
Standardmäßig sind alle Optionen aktiviert.
DISCLAIMER: Das Durchlaufen der Tests ist keine Garantie dafür, dass die Aufgaben vollständig korrekt implementiert sind.
<br>
## ListItemTest
### .checkClass()* / .classDefinitionCorrect()
Überprüft, ob die Definition der Klasse `ListItem` korrekt ist. Testet, dass …
- die Klasse generisch ist und einen Typparameter `T` hat
- die Klasse nicht abstrakt ist
- die Klasse einen parameterlosen Konstruktor hat
- die Klasse die Attribute `key` vom Typ `T`und `next` vom Typ `ListItem<T>` hat
## MyTreeNodeTest
Setzt voraus, dass `ListItem` richtig definiert ist.
### .checkClass()*
Überprüft, ob die Definition der Klasse `MyTreeNode` korrekt ist. Testet, dass …
- die Klasse public ist
- die Klasse nicht generisch ist
- die Klasse nicht abstrakt ist
- die Klasse einen public Konstruktor mit formalem Typ `long` als ersten Parameter hat
- die Klasse die im Übungsblatt vorausgesetzten Attribute `nodeID` und `successors` besitzt
### .testFields()
Testet, ob die Attribute eines Objekts der Klasse die korrekten Werte nach der Instantiierung haben.
## MyTreeTest
Setzt voraus, dass `MyTreeNode` richtig definiert ist.
### .checkClass()*
Überprüft, ob die Definition der Klasse `MyTree` korrekt ist. Testet, dass …
- die Klasse nicht generisch ist
- die Klasse das Interface `Iterable<Character>` implementiert und nicht abstrakt ist
- die Klasse einen public Konstruktor mit einem Parameter vom formalen Typ `Reader` und einen Parameter vom formalen Typen `boolean` hat
- die Klasse die im Übungsblatt vorausgesetzten Attribute `root` und `nextNodeID` besitzt
### .testConstructor(String, Boolean)
Überprüft, ob der Konstruktor die Variable `root` richtig initialisiert und testet dabei indirekt die Methoden `buildRecursively` und `buildIteratively`. Der Konstruktor wird mit vorgegebenen Strings (valide sowie invalide Klammerausdrücke) als ersten Parameter und zufällig mit `true` und `false` als zweiten Parameter aufgerufen.
### .testIterator()
Überprüft, ob die Methode `iterator` eine Instanz von `MyParenthesesTreeIterator` zurückgibt.
### .testIsIsomorphic(String)
Überprüft, ob die Methode `isIsomorphic` korrekt funktioniert.
## ParenthesesTreeIteratorTest
Setzt voraus, dass `MyTree` richtig definiert ist.
### .checkClass()*
Überprüft, ob die Definition der Klasse `MyParenthesesTreeIterator` korrekt ist. Testet, dass …
- die Klasse nicht generisch ist
- die Klasse das Interface `Iterator<Character>` implementiert und nicht abstrakt ist
### .testNext(String)
Setzt voraus, dass `MyTree#iterator()` richtig definiert ist.
Überprüft, ob die Methode `next` mit zufälligen Klammerausdrücken wie erwartet funktioniert.
### .testHasNext(String)
Setzt voraus, dass `MyTree#iterator()` und `MyParenthesesTreeIterator#next()` richtig definiert sind.
Überprüft, ob die Methode `hasNext` mit zufälligen Klammerausdrücken wie erwartet funktioniert.
### .testRemove()
Überprüft, ob die Methode `remove` wie erwartet nur eine `UnsupportedOperationException` wirft.
### Version 1.0
Initial tests \
Addendum: Optimized RandomTreeProvider
\ No newline at end of file
package h05;
import org.opentest4j.AssertionFailedError;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.lang.reflect.Modifier.*;
import static org.junit.jupiter.api.Assertions.*;
@SuppressWarnings("unused")
public class Assertions {
public static final String[] UNBOUNDED = new String[] {Object.class.getTypeName()};
private static final Map<Integer, Object[]> MODIFIER_METHODS;
static {
Map<Integer, Object[]> modifierMethodsTmp = null;
try {
modifierMethodsTmp = new HashMap<>() {{
put(PUBLIC, new Object[] {Modifier.class.getDeclaredMethod("isPublic", int.class), "public"});
put(PRIVATE, new Object[] {Modifier.class.getDeclaredMethod("isPrivate", int.class), "private"});
put(PROTECTED, new Object[] {Modifier.class.getDeclaredMethod("isProtected", int.class), "protected"});
put(STATIC, new Object[] {Modifier.class.getDeclaredMethod("isStatic", int.class), "static"});
put(FINAL, new Object[] {Modifier.class.getDeclaredMethod("isFinal", int.class), "final"});
put(SYNCHRONIZED, new Object[] {Modifier.class.getDeclaredMethod("isSynchronized", int.class), "synchronized"});
put(VOLATILE, new Object[] {Modifier.class.getDeclaredMethod("isVolatile", int.class), "volatile"});
put(TRANSIENT, new Object[] {Modifier.class.getDeclaredMethod("isTransient", int.class), "transient"});
put(NATIVE, new Object[] {Modifier.class.getDeclaredMethod("isNative", int.class), "native"});
put(INTERFACE, new Object[] {Modifier.class.getDeclaredMethod("isInterface", int.class), "an interface"});
put(ABSTRACT, new Object[] {Modifier.class.getDeclaredMethod("isAbstract", int.class), "abstract"});
put(STRICT, new Object[] {Modifier.class.getDeclaredMethod("isStrict", int.class), "strict"});
}};
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
MODIFIER_METHODS = modifierMethodsTmp;
}
/**
* Assert that the given member has the specified modifiers.
* <br><br>
* Calls {@link Assertions#assertHasModifiers(Member, String, Integer...)} with {@code null} as error
* message (default error message)
* @param member the member to check the modifiers of
* @param modifiers a list of modifiers (constants of {@link Modifier})
* @throws IllegalArgumentException if a value does not correspond to a modifier
*/
public static void assertHasModifiers(Member member, Integer... modifiers) {
assertHasModifiers(member, null, modifiers);
}
/**
* Assert that the given member has the specified modifiers.
* @param member the member to check the modifiers of
* @param modifiers a list of modifiers (constants of {@link Modifier})
* @param errorMsg the error message to pass to {@link AssertionError}
* @throws IllegalArgumentException if a value does not correspond to a modifier
*/
public static void assertHasModifiers(Member member, String errorMsg, Integer... modifiers) {
try {
for (Integer modifier : modifiers) {
Object[] value = MODIFIER_METHODS.getOrDefault(modifier, null);
if (value == null)
throw new IllegalArgumentException(String.valueOf(modifier));
Method method = (Method) value[0];
String msgSuffix = (String) value[1];
if (arrayContains(modifier, modifiers))
assertTrue((Boolean) method.invoke(null, member.getModifiers()),
errorMsg != null ? errorMsg : member.getName() + " must be " + msgSuffix);
}
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
/**
* Assert that the given class has the specified modifiers.
* <br><br>
* Calls {@link Assertions#assertHasModifiers(Class, String, Integer...)} with {@code null} as error
* message (default error message)
* @param c the member to check the modifiers of
* @param modifiers a list of modifiers (constants of {@link Modifier})
*/
public static void assertHasModifiers(Class<?> c, Integer... modifiers) {
assertHasModifiers(c, null, modifiers);
}
/**
* Assert that the given class has the specified modifiers.
* @param c the member to check the modifiers of
* @param errorMsg the error message to pass to {@link AssertionError}
* @param modifiers a list of modifiers (constants of {@link Modifier})
*/
public static void assertHasModifiers(Class<?> c, String errorMsg, Integer... modifiers) {
try {
for (Integer modifier : modifiers) {
Object[] value = MODIFIER_METHODS.getOrDefault(modifier, null);
if (value == null)
throw new IllegalArgumentException(String.valueOf(modifier));
Method method = (Method) value[0];
String msgSuffix = (String) value[1];
if (arrayContains(modifier, modifiers))
assertTrue((Boolean) method.invoke(null, c.getModifiers()),
errorMsg != null ? errorMsg : c.getName() + " must be " + msgSuffix);
}
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
/**
* Assert that the given member does not have the specified modifiers.
* <br><br>
* Calls {@link Assertions#assertDoesNotHaveModifiers(Member, String, Integer...)} with {@code null} as error
* message (default error message)
* @param member the member to check the modifiers of
* @param modifiers a list of modifiers (constants of {@link Modifier})
* @throws IllegalArgumentException if a value does not correspond to a modifier
*/
public static void assertDoesNotHaveModifiers(Member member, Integer... modifiers) {
assertDoesNotHaveModifiers(member, null, modifiers);
}
/**
* Assert that the given member does not have the specified modifiers.
* @param member the member to check the modifiers of
* @param modifiers a list of modifiers (constants of {@link Modifier})
* @param errorMsg the error message to pass to {@link AssertionError}
* @throws IllegalArgumentException if a value does not correspond to a modifier
*/
public static void assertDoesNotHaveModifiers(Member member, String errorMsg, Integer... modifiers) {
try {
for (Integer modifier : modifiers) {
Object[] value = MODIFIER_METHODS.getOrDefault(modifier, null);
if (value == null)
throw new IllegalArgumentException(String.valueOf(modifier));
Method method = (Method) value[0];
String msgSuffix = (String) value[1];
if (arrayContains(modifier, modifiers))
assertFalse((Boolean) method.invoke(null, member.getModifiers()),
errorMsg != null ? errorMsg : member.getName() + " must not be " + msgSuffix);
}
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
/**
* Assert that the given class does not have the specified modifiers.
* <br><br>
* Calls {@link Assertions#assertDoesNotHaveModifiers(Class, String, Integer...)} with {@code null} as error
* message (default error message)
* @param c the member to check the modifiers of
* @param modifiers a list of modifiers (constants of {@link Modifier})
*/
public static void assertDoesNotHaveModifiers(Class<?> c, Integer... modifiers) {
assertDoesNotHaveModifiers(c, null, modifiers);
}
/**
* Assert that the given class does not have the specified modifiers.
* @param c the member to check the modifiers of
* @param errorMsg the error message to pass to {@link AssertionError}
* @param modifiers a list of modifiers (constants of {@link Modifier})
*/
public static void assertDoesNotHaveModifiers(Class<?> c, String errorMsg, Integer... modifiers) {
try {
for (Integer modifier : modifiers) {
Object[] value = MODIFIER_METHODS.getOrDefault(modifier, null);
if (value == null)
throw new IllegalArgumentException(String.valueOf(modifier));
Method method = (Method) value[0];
String msgSuffix = (String) value[1];
if (arrayContains(modifier, modifiers))
assertFalse((Boolean) method.invoke(null, c.getModifiers()),
errorMsg != null ? errorMsg : c.getName() + " must not be " + msgSuffix);
}
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
/**
* Assert that the given class is generic and its type parameters have the given bounds
* @param c the class to check
* @param expectedTypeParameters a map of expected type parameters and their bounds
*/
public static void assertIsGeneric(Class<?> c, Map<String, String[]> expectedTypeParameters) {
int i = 0;
TypeVariable<?>[] actualTypeParameters = c.getTypeParameters();
assertNotEquals(0, actualTypeParameters.length, c.getName() + " must be generic");
assertEquals(expectedTypeParameters.size(), actualTypeParameters.length,
c.getName() + " must have exactly " + expectedTypeParameters.size() + " type parameters");
for (Map.Entry<String, String[]> expectedTypeParameter : expectedTypeParameters.entrySet()) {
TypeVariable<?> actualTypeParameter = actualTypeParameters[i++];
assertEquals(expectedTypeParameter.getKey(), actualTypeParameter.getTypeName(), "Unexpected type parameter");
assertEquals(expectedTypeParameter.getValue().length, actualTypeParameter.getBounds().length, "Number of bounds differ");
for (String bound : expectedTypeParameter.getValue())
if (!arrayContains(bound, Arrays.stream(actualTypeParameter.getBounds()).map(Type::getTypeName).toArray()))
throw new AssertionFailedError(
c.getName() + " is missing a required bound for type parameter " + expectedTypeParameter.getKey(),
bound, null);
}
}
/**
* Assert that the given class is not generic
* @param c the class to check
*/
public static void assertNotGeneric(Class<?> c) {
assertEquals(0, c.getTypeParameters().length, c.getName() + " must not be generic");
}
/**
* Assert that the given field has the type {@code typeName}
* @param field the field to check
* @param typeName the type (type name)
*/
public static void assertType(Field field, String typeName) {
assertEquals(typeName, field.getGenericType().getTypeName(), "Type of field " + field.getName() + " is incorrect");
}
/**
* Assert that the given method has the return type {@code typeName}
* @param method the method to check
* @param typeName the return type (type name)
*/
public static void assertReturnType(Method method, String typeName) {
assertEquals(typeName, method.getGenericReturnType().getTypeName(), "Return type of method " + method.getName() + " is incorrect");
}
/**
* Assert that a class implements all given interfaces
* @param c the class to check
* @param interfaces the interfaces the class has to implement (type name)
*/
public static void assertImplements(Class<?> c, String... interfaces) {
List<String> actualInterfaces = Arrays.stream(c.getGenericInterfaces()).map(Type::getTypeName).collect(Collectors.toList());
for (String intf : interfaces)
if (!actualInterfaces.contains(intf))
throw new AssertionFailedError("Required interface not implemented in " + c.getName(), intf, null);
}
private static boolean arrayContains(Object needle, Object[] stack) {
for (Object stackElement : stack)
if (Objects.equals(needle, stackElement))
return true;
return false;
}
}
package h05;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class ExampleJUnitTest {
@Test
public void testAddition() {
assertEquals(2, 1 + 1);
}
}
package h05;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Map;
import static h05.Assertions.*;
import static h05.Utils.*;
import static h05.Utils.TestType.Type.CLASS;
import static java.lang.reflect.Modifier.*;
import static org.junit.jupiter.api.Assertions.*;
@TestType(CLASS)
public class ListItemTest {
public static Class<?> listItemClass;
public static Constructor<?> constructor;
public static Field key, next;
private static final String CLASS_NAME = "h05.ListItem";
@BeforeAll
public static void checkClass() {
if (CLASS_CORRECT.containsKey(ListItemTest.class))
return;
listItemClass = getClassForName(CLASS_NAME);
// is generic
assertIsGeneric(listItemClass, Map.of("T", UNBOUNDED));
// is not abstract
assertDoesNotHaveModifiers(listItemClass, ABSTRACT);
// constructors
try {
constructor = listItemClass.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
fail(CLASS_NAME + " must have a parameterless constructor", e);
}
// fields
try {
key = listItemClass.getDeclaredField("key");
next = listItemClass.getDeclaredField("next");
} catch (NoSuchFieldException e) {
fail(CLASS_NAME + " is missing one or more required fields", e);
}
key.setAccessible(true);
next.setAccessible(true);
assertHasModifiers(key, PUBLIC);
assertType(key, "T");
assertHasModifiers(next, PUBLIC);
assertType(next, "h05.ListItem<T>");
CLASS_CORRECT.put(ListItemTest.class, true);
}
@Test
public void classDefinitionCorrect() {}
}
package h05;
import h05.provider.RandomTreeProvider;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import java.util.Iterator;
import java.util.NoSuchElementException;
import static h05.Assertions.*;
import static h05.Utils.TestType.Type.CLASS;
import static h05.Utils.*;
import static java.lang.reflect.Modifier.*;
import static org.junit.jupiter.api.Assertions.*;
@TestType(CLASS)
public class MyParenthesesTreeIteratorTest {
public static Class<?> myParenthesesTreeIteratorClass;
private static final String CLASS_NAME = "h05.MyParenthesesTreeIterator";
@BeforeAll
public static void checkClass() {
if (CLASS_CORRECT.containsKey(MyParenthesesTreeIteratorTest.class))
return;
requireTest(MyTreeTest.class);
myParenthesesTreeIteratorClass = getClassForName(CLASS_NAME);
// modifiers
assertDoesNotHaveModifiers(myParenthesesTreeIteratorClass, ABSTRACT);
// is not generic
assertNotGeneric(myParenthesesTreeIteratorClass);
// implements Iterator<Character>
assertImplements(myParenthesesTreeIteratorClass, "java.util.Iterator<java.lang.Character>");
CLASS_CORRECT.put(MyParenthesesTreeIteratorTest.class, true);
}
@ParameterizedTest
@ArgumentsSource(RandomTreeProvider.class)
public void testNext(String treeString) throws ReflectiveOperationException {
requireTest(MyTreeTest.class.getDeclaredMethod("testIterator"));
Iterator<Character> iterator = MyTreeTest.getIterator(treeString);
StringBuilder builder = new StringBuilder();
for (int i = 0; i < treeString.length(); i++)
try {
Character next = iterator.next();
builder.append(next != null ? next : "");
} catch (NoSuchElementException e) {
fail("Iterator has fewer elements than expected: " + (i + 1) + "/" + treeString.length(), e);
}
assertEquals(treeString, builder.toString(), "Characters returned by iterator does not equal expected");
assertThrows(NoSuchElementException.class, iterator::next, "Iterator has more elements than expected");
}
@ParameterizedTest
@ArgumentsSource(RandomTreeProvider.class)
public void testHasNext(String treeString) throws ReflectiveOperationException {
requireTest(MyTreeTest.class.getDeclaredMethod("testIterator"));
requireTest(MyParenthesesTreeIteratorTest.class.getDeclaredMethod("testNext", String.class), "()");
Iterator<Character> iterator = MyTreeTest.getIterator(treeString);
for (int i = 0; i < treeString.length(); i++, iterator.next())
assertTrue(iterator.hasNext(),
"Iterator should not have reached end yet, number of elements: " + (i + 1) + "/" + treeString.length());
assertFalse(iterator.hasNext(), "Iterator has more elements than expected");
}
@Test
public void testRemove() throws ReflectiveOperationException {
Iterator<Character> iterator = MyTreeTest.getIterator("");
assertThrows(UnsupportedOperationException.class, iterator::remove);
}
}
package h05;
import h05.Utils.TestType;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import static h05.Assertions.*;
import static h05.Utils.*;
import static h05.Utils.TestType.Type.CLASS;
import static java.lang.reflect.Modifier.*;
import static org.junit.jupiter.api.Assertions.*;
@TestType(CLASS)
public class MyTreeNodeTest {
public static Class<?> myTreeNodeClass;
public static Constructor<?> constructor;
public static Field nodeID, successors;
private static final String CLASS_NAME = "h05.MyTreeNode";
@BeforeAll
public static void checkClass() {
if (CLASS_CORRECT.containsKey(MyTreeNodeTest.class))
return;
requireTest(ListItemTest.class);
myTreeNodeClass = getClassForName(CLASS_NAME);
// modifiers
assertHasModifiers(myTreeNodeClass, PUBLIC);
assertDoesNotHaveModifiers(myTreeNodeClass, ABSTRACT);
// is not generic
assertNotGeneric(myTreeNodeClass);
// constructors