Der Korrekturdienst Python3 Unit Tests steht Ihnen bei Codeeingabe-Aufgaben zur Verfügung. Von Studierenden eingegebener Python3-Code wird mit Hilfe von Unit Tests auf korrektes Funktionieren überprüft.
Wählen Sie den Korrekturdienst aus, indem Sie auf das Korrekturdienste-Symbol (1) klicken. Es öffnet sich die Seitenleiste mit dem Register Korrektur. Klicken Sie hier auf den Button Benutzen unter Python3 Unit Tests (2).
In der folgenden Beispielaufgabe sollen die Studierenden drei Funktionen erstellen, die dann vom Korrekturdienst auf Korrektheit überprüft werden.
Um die Aufgabe anzulegen, gehen Sie wie folgt vor:
Erstellen Sie eine Codeeingabe-Aufgabe.
Klicken Sie auf das Korrekturdienste-Symbol (1). Die Seitenleiste öffnet sich. Klicken Sie anschließend auf den Button Benutzen (2), um den Korrekturdienst auszuwählen. Tragen Sie die zu verwendende Bibliothek import math
in das Quellcode-Feld ein. Diese Zeile wird den Studierenden bei der Bearbeitung der Aufgabe angezeigt. Klicken Sie anschließend auf Auswahl sperren, damit der vorgegebene Code in der Prüfung nicht verändert werden kann.
Die Aufgabe besteht aus drei Testcases. Legen Sie den ersten Testcase an, indem Sie auf den Button Neuer Eintrag für "Python3 Unit Tests" klicken.
Tragen Sie für den ersten Testcase mit der Aufgabenstellung "Schreiben Sie eine Funktion is_prime(n)..." den folgenden Code in das Quellcode-Feld Testcase ein:
def test_is_prime(self):
self.assertFalse(is_prime(4))
self.assertTrue(is_prime(2))
self.assertTrue(is_prime(3))
self.assertFalse(is_prime(8))
self.assertFalse(is_prime(10))
self.assertTrue(is_prime(7))
self.assertEqual(is_prime(-3), "Negative numbers are not allowed")
Bei mehreren Testcases (in diesem Beispiel sind es drei) können Sie über das Feld Gewichtung für jeden Testcase einstellen, wie hoch der Anteil der zu erreichenden Punkte von der Gesamtpunktzahl ist, wenn der Test erfolgreich war. Standardmäßig ist die Gewichtung für jeden Testcase mit "1" eingestellt.
Beispiel für eine andere Gewichtung: Bei einer Aufgabe mit insgesamt 3 Testcases ist die Gewichtung für einen Testcase mit "2" festgelegt, bei den anderen beiden mit "1". Der erste Testcase würde also 50% der Gesamtpunktzahl einbringen, die anderen beiden jeweils 25%.
Das Aktivieren der Checkbox Muss erfüllt sein um weitere Testcases auszuführen" bewirkt, dass - sollte der Test für diesen Testcase fehlschlagen - alle nachfolgenden Testcases nicht mehr ausgeführt werden.
def test_cubic(self):
self.assertEqual(cubic(2), 8)
self.assertEqual(cubic(-2), -8)
self.assertNotEqual(cubic(2), 4)
self.assertNotEqual(cubic(-3), 27)
Beachten Sie, dass die Namen der Funktionen in den einzelnen Testcases immer mit "test" anfangen müssen.
def test_say_hello(self):
self.assertEqual(say_hello("Max"), "Hello, Max")
self.assertEqual(say_hello("Lisa"), "Hello, Lisa")
self.assertNotEqual(say_hello("Tom"), "Hi, Tom")
self.assertNotEqual(say_hello("Test"), "Hi, Test")
Mögliche Lösung:
def is_prime(n):
if n < 0:
return 'Negative numbers are not allowed'
if n <= 1:
return False
if n == 2:
return True
if n == 3:
return True
if n % 2 == 0:
return False
for i in range(2, int(math.sqrt(n)) + 1):
if n % i == 0:
return False
return True
def cubic(a):
return a * a * a
def say_hello(name):
return "Hello, " + name
Die folgende Beispielaufgabe besteht aus zwei Teilaufgaben: In Teilaufgabe A soll eine Funktionen erstellt werden, die dann vom Korrekturdienst auf Korrektheit überprüft wird. Dabei können Teilpunkte für bestimmte Bewertungskriterien erzielt werden. In Teilaufgabe B soll dann basierend auf der erstellten Funktion aus Teilaufgabe A eine zweite Funktion implementiert werden.
Die Aufgabe der Studierenden ist es, eine Funktion subtrahiere
zu implementieren, die a-b
durchführen soll. Dies kann auf unterschiedliche Weise gelöst werden, eine mögliche Lösung wäre:
def substrahiere(a, b):
return a - b
Im Aufgabeneditor ist die Aufgabe nun wie folgt konfiguriert:
A: Name des Elements
B: Nummer des Testcases
C: Wert im Feld Gewichtung = anteilmäßige Punkte von der Gesamtpunktzahl bei korrekter Lösung in diesem Bewertungsabschnitt
D: ist aktiviert, d.h. schlägt dieser Testcase fehl, werden weitere Testcases nicht mehr ausgeführt
Für diesen Teil der Aufgabe sind 3 Testcases hinterlegt:
Der hier hinterlegte Code überprüft, ob eine Funktion "subtrahiere" mit zwei Parametern definiert wurde. Ist dies nicht der Fall, schlägt der Testcase fehl und der Studierende erhält eine entsprechende Rückmeldung.
def test_func_name_and_params(self):
pcm = PyCheckMate(student_answer)
check_func_name = pcm.has_function("subtrahiere", 2)
if not(check_func_name["passed"]):
self.fail(check_func_name["note"])
Ist dies der Fall, werden die Punkte für diesen Testcase vergeben (Eintrag im Feld Gewichtung).
Der hier hinterlegte Code überprüft den Rückgabetypen der geschriebenen Funktion. Dieser muss entweder eine Ganzzahl (int) oder eine Fließkommazahl (float) sein.
def test_return_type(self):
try:
a = 1
b = 2
student_solution = subtrahiere(a, b)
except Exception as e:
self.fail("Bei der Ausführung Ihrer Funktion ist ein Problem aufgetreten. Siehe oben.")
feedback = f"\nFalscher Rückgabetyp. subtrahiere({a}, {b}) sollte eine Zahl (int / float) zurückgeben und keine {type(student_solution)}."
nose.tools.assert_is_instance(student_solution, (int, float), feedback)
Ist dies der Fall, werden die Punkte für diesen Testcase vergeben.
Für Testcase 3 wird zunächst im Bereich Vorangestellter Code eine Musterlösung definiert:
def subtrahiere_sample_solution(a, b):
return a - b
def subtrahiere_wrong_params(a, b):
return b - a
Der für den Testcase hinterlegte Code überprüft dann, ob tatsächlich a-b
gerechnet und zurückgegeben wird, für den Fall, dass a
der erste und b
der zweite Parameter ist.
import random
def test_multiple_inputs(self):
try:
a = random.randint(1, 10)
b = random.randint(1, 10)
student_solution = subtrahiere(a, b)
except Exception as e:
self.fail("Bei der Ausführung Ihrer Funktion ist ein Problem aufgetreten. Siehe oben.")
sample_solution = subtrahiere_sample_solution(a, b)
sample_solution_wrong_params = subtrahiere_wrong_params(a, b)
if student_solution == sample_solution_wrong_params:
feedback = f"\nFalscher Reihenfolge der Parameter. subtrahiere({a}, {b}) sollte den Wert {sample_solution} zurückgeben und nicht {student_solution}."
self.fail(feedback)
elif student_solution != sample_solution:
feedback = f"\nFalscher Rückgabewert. subtrahiere({a}, {b}) sollte den Wert {sample_solution} zurückgeben und nicht {student_solution}."
self.fail(feedback)
Ist dies der Fall, werden die Punkte für diesen Testcase vergeben.
Basierend auf der in Teilaufgabe A erstellten Funktion subtrahiere
soll nun eine neue Funktion subtrahiere_liste(liste, zahl)
erstellt werden.
Die Funktion soll aus den Parametern liste
und zahl
bestehen und für jedes Element in der Liste die Funktion subtrahiere
verwenden, um die übergebene Zahl abzuziehen. Das Ergebnis wird in einer neuen Liste gespeichert, die zurückgegeben wird.
Mit Hilfe von 4 Testcases wird nun die Korrektheit der einzelnen Aufgabenelemente überprüft.
Im Bereich Vorangestellter Code wird die Lösung aus Teilaufgabe A übernommen, da diese Lösung später in den Testcases verwendet wird. Funktioniert dies nicht, wird die Funktion vordefiniert.
# Try to run code from a) in order to have the correct version
try:
if aufgabe_subtrahiere == "":
raise Exception()
exec(aufgabe_subtrahiere)
if not hasattr(__builtins__, 'subtrahiere'):
raise Exception()
except:
def subtrahiere(a, b):
return a - b
Der hinterlegte Code überprüft, ob eine Lösung erstellt wurde und die Funktion subtrahiere_liste
heißt.
def test_func_name_and_params(self):
pcm = PyCheckMate(student_answer)
check_func_name = pcm.has_function("subtrahiere_liste", 2)
if not(check_func_name["passed"]):
self.fail(check_func_name["note"])
Ist dies der Fall, werden die Punkte für diesen Testcase vergeben.
Im Bereich Vorangestellter Code wird die Lösung aus Teilaufgabe A übernommen, da diese Lösung später in den Testcases verwendet wird.
# Try to run code from a) in order to have the correct version
try:
if aufgabe_subtrahiere == "":
raise Exception()
exec(aufgabe_subtrahiere)
if not hasattr(__builtins__, 'subtrahiere'):
raise Exception()
except:
def subtrahiere(a, b):
return a - b
Der hinterlegte Code überprüft den Rückgabetypen der geschriebenen Funktion. Dieser muss eine Liste list
sein.
def test_return_type(self):
try:
test_liste = random.sample(range(10, 30), 5)
test_num = random.randint(1, 3)
student_solution = subtrahiere_liste(test_liste, test_num)
except Exception as e:
self.fail("Bei der Ausführung Ihrer Funktion ist ein Problem aufgetreten. Siehe oben.")
feedback = f"\nFalscher Rückgabetyp. subtrahiere_liste({test_liste}, {test_num}) sollte eine Liste zurückgeben und keine {type(student_solution)}."
nose.tools.assert_is_instance(student_solution, list, feedback)
Ist dies der Fall, werden die Punkte für diesen Testcase vergeben.
Im Bereich Vorangestellter Code wird die Lösung aus Teilaufgabe A übernommen, da diese Lösung später in den Testcases verwendet wird. Die Funktion subtrahiere_liste_sample_solution
subtrahiert von jedem Element aus liste
den Wert der in zahl
gespeichert ist. Das Ergebnis ist eine neue Liste, die alle Ergebnisse der Subtraktionen enthält.
# Try to run code from a) in order to have the correct version
try:
if aufgabe_subtrahiere == "":
raise Exception()
exec(aufgabe_subtrahiere)
if not hasattr(__builtins__, 'subtrahiere'):
raise Exception()
except:
def subtrahiere(a, b):
return a - b
def subtrahiere_sample_solution(a, b):
return a - b
def subtrahiere_liste_sample_solution(liste, zahl):
neue_liste = []
for elem in liste:
neue_liste.append(subtrahiere_sample_solution(elem, zahl))
return neue_liste
Der hinterlegte Code generiert dann eine Liste test_liste
mit zufälligen Zahlen und eine zufällige Zahl test_num
. Diese werden als Argument sowohl für die Lösung des Studierenden als auch für die Musterlösung verwendet. Anschließend werden die Ergebnisse beider Funktionen verglichen.
import random
def test_multiple_inputs(self):
try:
test_liste = random.sample(range(10, 30), 5)
test_num = random.randint(1, 3)
student_solution = subtrahiere_liste(test_liste, test_num)
except Exception as e:
self.fail("Bei der Ausführung Ihrer Funktion ist ein Problem aufgetreten. Siehe oben.")
sample_solution = subtrahiere_liste_sample_solution(test_liste, test_num)
# Check if all values are correct
if not isinstance(student_solution, list):
self.fail("Keine weiteren Tests aufgrund eines falschen Rückgabetypens der Funktion möglich!")
# Check length of return value
if len(student_solution) != len(sample_solution):
self.fail(f"Falsche Anzahl an Elementen in der zurückgegeben Liste vorhanden.\nEs sollten {len(sample_solution)} Elemente enthalten sein, es sind jedoch {len(student_solution)} enthalten!")
# Check each elem of return value
wrong_indices = []
for index, student_elem in enumerate(student_solution):
if sample_solution[index] != student_elem:
wrong_indices.append(index)
if len(wrong_indices) > 0:
self.fail(f"Die zurückgegebene Liste enthält nicht die korrekten Werte. An den Positionen mit Index {wrong_indices} sind die Werte nicht korrekt.")
Ist dies der Fall, werden die Punkte für diesen Testcase vergeben.
Im Bereich Vorangestellter Code wird die Lösung aus Teilaufgabe A übernommen, da diese Lösung später in den Testcases verwendet wird.
# Try to run code from a) in order to have the correct version
try:
if aufgabe_subtrahiere == "":
raise Exception()
exec(aufgabe_subtrahiere)
if not hasattr(__builtins__, 'subtrahiere'):
raise Exception()
except:
def subtrahiere(a, b):
return a - b
def subtrahiere_sample_solution(a, b):
return a - b
def subtrahiere_liste_sample_solution(liste, zahl):
neue_liste = []
for elem in liste:
neue_liste.append(subtrahiere_sample_solution(elem, zahl))
return neue_liste
Der hinterlegte Code überprüft, ob die Funktion subtrahiere
innerhalb der Funktion subtrahiere_liste
aufgerufen wurde.
import random
def test_subtrahiere_was_called(self):
# namesapce is answer_module as __main__ is not defined
with mock.patch(answer_module + '.subtrahiere') as mock_subtrahiere:
# Generate test values for arguments
test_liste = random.sample(range(10, 30), 5)
test_num = random.randint(1, 3)
# Call the student function
student_result = subtrahiere_liste(test_liste, test_num)
# Expected result
expected_result = subtrahiere_liste_sample_solution(test_liste, test_num)
# Überprüfen, ob subtrahiere für jedes Element der Liste aufgerufen wurde
expected_calls = [(elem, test_num) for elem in test_liste]
actual_student_calls = [call[0] for call in mock_subtrahiere.call_args_list]
# Check for the calls of the function
if len(actual_student_calls) == 0:
self.fail(f"Die Funktion subtrahiere wurde nicht innerhalb von subtrahiere_liste aufgerufen.")
if len(expected_calls) != len(actual_student_calls):
self.fail(f"Die Funktion subtrahiere wurde in subtrahiere_liste nicht so oft aufgerufen, wie es erforderlich ist.\nÜberprüfe, ob die Schleife oder der Code, der die Funktion aufruft, korrekt eingerichtet ist, damit sie die richtige Anzahl an Durchläufen hat.")
Ist dies der Fall, werden die Punkte für diesen Testcase vergeben.
Bei Fehlern im Python3-Code einer Aufgabe, die erst nach der Prüfungsdurchführung auffallen, sind nachträgliche Code-Anpassungen möglich. Klicken Sie im Schritt Korrektur einer Prüfung neben der entsprechenden Aufgabe auf den Button Korrigieren. Klicken Sie dann auf das Korrekturdienste-Symbol und nehmen Sie die Anpassungen im Code vor. Klicken Sie dann auf den Button Korrektur speichern (links am Ende der Aufgabe), startet dies eine neue Korrektur.
Beachten Sie, dass Sie die Änderung für jede Variante einzeln übernehmen müssen.
Um zusätzliche Systemlast zu vermeiden, wird nur das korrigiert, was geändert wurde. Jedes Element wird autark korrigiert, damit es nicht zu Fehlern bei Abhängigkeiten kommen kann. Die Korrekturen beziehen sich also immer nur auf die Eingaben der Studierenden und die Variablen aus der Parametrisierung, aber nie auf Variablen oder Ergebnisse aus anderen Korrekturskripten.