Java >> Tutoriel Java >  >> Java

Comment j'ai automatisé mes responsabilités de notation

Depuis que j'ai commencé à enseigner, j'ai essayé de trouver des moyens d'automatiser mes responsabilités de notation. Après tout, cela prend beaucoup de temps et je ne trouve pas que cela soit extrêmement utile pour les étudiants. Chaque minute que j'économise grâce à l'automatisation me permet de fournir des commentaires de qualité, de me concentrer sur mon enseignement et d'améliorer ma santé mentale. Qui pourrait dire non à ça ?

Responsabilités de notation

J'ai mentionné mes responsabilités en matière de notation à quelques reprises dans cette série, mais je me suis dit que cela ne ferait pas de mal de les décrire une fois de plus.

En plus d'enseigner, je suis responsable de la notation de 12 projets, 14 devoirs, 14 laboratoires et 3 examens par semestre. Multipliez tous ces nombres par 40, et c'est le nombre total de devoirs que je note au cours d'un semestre. Comme vous pouvez probablement l'imaginer, c'est une énorme perte de temps en dehors de la salle de classe.

Pour accélérer les choses, j'ai essayé de trouver des moyens d'automatiser la notation. La plus grande opportunité de gagner du temps est peut-être les projets qui peuvent prendre environ 6 heures par semaine pour être notés. Malheureusement, cette longue durée est due à une poignée de problèmes :

  • Les projets valent le plus de points, ils nécessitent donc le plus de commentaires.
  • Les projets doivent être testés, ce qui peut prendre un certain temps en fonction de la complexité du programme.
  • Les projets sont organisés en packages, ils doivent donc être transférés dans des formats d'archive tels que zip.
  • Les projets sont écrits par les étudiants, donc le style varie énormément, ce qui rend le code difficile à lire.

Comme vous pouvez le voir, il existe de nombreuses exigences strictes pour les projets qui peuvent faire de la notation une tâche très chronophage. Pour ajouter l'insulte à l'injure, les étudiants ont tendance à ne pas suivre les instructions, de sorte que les fichiers doivent parfois être modifiés avant de pouvoir être exécutés. Dans le pire des cas :je dois contacter les étudiants car ils n'ont pas tout envoyé.

Automatisation de la notation

En tant que personne qui essaie toujours de tirer le meilleur parti de l'efficacité des tâches quotidiennes, j'ai rapidement pris l'initiative d'automatiser la notation des projets. Pour être honnête, je ne pouvais tout simplement pas imaginer terminer la procédure suivante pour 40 étudiants sans devenir fou :

  1. Télécharger la solution étudiante.
  2. Décompressez la solution étudiante.
  3. Charger le(s) fichier(s) dans l'IDE.
  4. Exécuter le(s) fichier(s) (répéter pour différents cas de test).
  5. Style de solution de jauge.
  6. Évaluer la solution en fonction des tests et du style
  7. Donnez votre avis.

Après avoir examiné cette liste, j'ai le sentiment d'avoir fait le bon choix d'automatiser ma notation, mais qu'implique exactement l'automatisation ? Jetons un coup d'œil.

Présentation de JUnit

Au cours de mon premier semestre, la meilleure option que j'avais à l'époque pour l'automatisation était le test JUnit. Au cours d'une semaine donnée, il me faudrait environ 90 minutes pour rédiger une solution JUnit pour le projet et 2 heures supplémentaires pour terminer la notation. En d'autres termes, j'ai réussi à réduire un processus de 6 heures à environ 4 heures. Je vais le prendre n'importe quel jour !

Bien sûr, JUnit n'était probablement pas le choix idéal. Après tout, nous n'enseignons pas les méthodes avant la 6ème semaine, donc la plupart des projets sont des méthodes principales massives. De plus, les étudiants ne suivent pas toujours les mêmes conventions de dénomination pour les classes, je dois donc être intelligent dans la façon dont j'appelle la méthode principale.

En conséquence, j'ai fini par écrire un ensemble assez complexe de méthodes pour deviner les noms de classe en utilisant la réflexion. Par exemple, la méthode suivante génère une liste de noms de classe pour la réflexion par force brute :

private static ArrayList<String> getTestClasses(int project) {
    ArrayList<String> toTest = new ArrayList<String>();
    toTest.add("osu.cse1223.Project%1$s");
    toTest.add("osu.cse1223.Project%1$sa");
    toTest.add("osu.cse1223.CSEProject%1$s");
    toTest.add("cse1223.Project%1$sa");
    toTest.add("cse1223.Project%1$s");
    toTest.add("project%1$s.Project%1$s");
    toTest.add("Project%1$s");
    toTest.add("Project%1$sA");
    toTest.add("osu.cse1223.DragonsGame");
    toTest.add("Project04.DragonTrainers");
    toTest.add("Main");
    String projectNumberWhole = Integer.toString(project);
    String projectNumberPad = "0" + projectNumberWhole;
    int originalSize = toTest.size();
    for (int i = 0; i < originalSize; i++) {
        String test = toTest.get(i);
        toTest.set(i, String.format(test, projectNumberPad));
        toTest.add(String.format(test, projectNumberWhole));
        toTest.add(String.format(test, projectNumberPad).toLowerCase());
        toTest.add(String.format(test, projectNumberWhole).toLowerCase());
    }
    return toTest;
}

De plus, étant donné que de nombreux projets exploitent la méthode principale et le formatage du texte, j'ai passé beaucoup de temps à capturer la sortie standard et à écrire sur l'entrée standard. Découvrez mes méthodes de configuration et de démontage :

@Before
public void setUp() {
    System.setOut(new PrintStream(outContent));
    System.setErr(new PrintStream(errContent));
}

@After
public void tearDown() {
    System.setIn(System.in);
    System.setOut(System.out);
}

Dans l'ensemble, la solution JUnit est assez maladroite, mais elle a fait le travail.

Décompresser le script

Alors que JUnit m'a fait gagner beaucoup de temps, il y avait encore des moyens de réduire le temps perdu. En particulier, j'ai constaté que je perdais beaucoup de temps à décompresser manuellement les dossiers.

Pour mettre un peu les choses en perspective, nous utilisons Canvas pour télécharger des solutions qui modifient un peu les noms de fichiers. En conséquence, les soumissions Java autonomes se retrouvent avec leurs noms de fichiers ruinés. Pour lutter contre ce problème, nous demandons aux étudiants d'exporter leurs solutions depuis Eclipse sous forme de fichiers zip. Cela aide de deux manières :

  1. Il protège les noms des fichiers Java sous-jacents.
  2. Il préserve la structure du package en cas de besoin.

Malheureusement, j'étais coincé à décompresser 41 fichiers chaque semaine. Certes, j'ai accéléré les choses avec 7-zip, mais je devais encore tout faire à la main.

Finalement, j'ai décidé d'automatiser ce processus de décompression en utilisant Python et la bibliothèque zipfile :

def extract_main_zip() -> str:
    """
    Extracts an archive given by the user.
    :return: the path to the unzipped archive
    """
    archive_name = filedialog.askopenfilename(
        title="Select Zip File",
        filetypes=(("zip files", "*.zip"), ("all files", "*.*"))
    )
    archive = zipfile.ZipFile(archive_name)
    archive_path = os.path.join(os.path.dirname(archive_name), ARCHIVE)
    archive.extractall(archive_path)
    archive.close()
    return archive_path

Dans cette fonction, j'utilise tk pour ouvrir une interface graphique de sélection de fichiers. À partir de là, je décompresse le fichier zip sélectionné et renvoie le chemin vers le site d'extraction.

Étant donné que le fichier zip contient des fichiers zip, j'ai décidé d'automatiser également ce processus de décompression :

def extract_solutions() -> str:
    """
    Extracts user folders.
    :return: the path to the extraction site
    """
    unzipped_archive = extract_main_zip()

    dump = os.path.join(os.path.dirname(unzipped_archive), DUMP)
    pathlib.Path(dump).mkdir(parents=True, exist_ok=True)

    for file in os.listdir(unzipped_archive):
        file_name = os.fsdecode(file)
        file_path = os.path.join(unzipped_archive, file_name)
        file_path_plus_name = os.path.join(dump, file_name.split("_")[0])
        if file_name.endswith(".zip"):
            zip_file = zipfile.ZipFile(file_path, "r")
            zip_file.extractall(file_path_plus_name)
            zip_file.close()
        else:
            name = file_name.split("_")[0]
            project = file_name.split("_")[-1]
            pathlib.Path(os.path.join(dump, name)).mkdir(parents=True, exist_ok=True)
            new_file_path = os.path.join(dump, name, project)
            os.rename(file_path, new_file_path)

    return dump

Comme nous pouvons le voir, cette fonction appelle la fonction précédente et stocke le chemin vers le site d'extraction. A partir de là, la fonction génère un nouveau site d'extraction appelé Dump .

Après cela, nous parcourons tous les fichiers zip, les extrayons et les plaçons dans un nouveau dossier avec le nom des étudiants comme nom de répertoire. Si nous rencontrons un fichier qui n'est pas un fichier zip, nous essayons de résoudre le problème de modification du nom avant de placer le fichier dans un dossier à côté de tous les fichiers zip extraits.

Lorsque nous avons terminé, nous retournons le chemin vers le nouveau site d'extraction. Au total, nous aurons deux nouveaux dossiers. Un qui contient tous les fichiers zip (Archives ), et une qui contient tous les fichiers décompressés (Dump ). À ce stade, les Archives répertoire est inutile, nous pourrions donc le supprimer.

Automatisation des tests

Avec le processus d'extraction automatisé, j'ai probablement gagné environ 30 secondes sur un fichier, ce qui représente un gain d'environ 20 minutes. Bien sûr, je prendrais ça n'importe quel jour.

Cela dit, j'avais l'impression qu'il y avait encore plus à faire. En particulier, j'ai trouvé qu'il était très long de faire ce qui suit :

  1. Téléchargez toutes les soumissions des étudiants.
  2. Exécutez le script d'extraction Python.
  3. Chargez Dr. Java.
  4. Faites glisser et déposez le fichier de test dans l'IDE.
  5. Notez la soumission de l'élève (répétez 40 fois).
    1. Récupérez le devoir d'un étudiant et déposez-le dans l'IDE.
    2. Test de réussite.
    3. Analyser les résultats des tests.
    4. Évaluer le style de soumission.
    5. Donnez votre avis.

Aussi ennuyeux que soit ce nouveau processus, c'était une amélioration incroyable par rapport au classement à la main. Au cours d'une semaine donnée, il se peut que je passe seulement 2 à 3 heures à évaluer des projets. Il serait idiot de dire que toute l'automatisation jusqu'à présent n'en valait pas la peine.

Cependant, il y a encore beaucoup d'étapes manuelles dans le processus ci-dessus, j'ai donc décidé de réduire à nouveau les étapes :

  1. Téléchargez toutes les soumissions des étudiants.
  2. Exécutez le script d'extraction et de test Python.
  3. Évaluer le style de soumission (répéter 40 fois)
  4. Donner des commentaires (répéter 40 fois)

Pour ce faire, j'ai étendu mon script Python pour prendre en charge les tests JUnit. À un niveau élevé, chaque solution est notée comme suit :

def grade_file(classes: str, build_file: str, test_class: str, results):
    """
    Grades a file.
    :param classes: a directory contain files under test
    :param build_file: a file to test
    :param test_class: the path to the test file
    :param results: the results file
    :return: None
    """
    classpath = "C:\\Program Files\\JUnit\\junit-4.13-beta-2.jar;C:\\Program Files\\JUnit\\hamcrest-all-1.3.jar;"

    compile_junit(classes, classpath, build_file)
    compilation_results = compile_junit(classes, classpath, test_class)
    execution_results = test_junit(classes, classpath, get_test_name(test_class))
    write_to_file(results, compilation_results, execution_results, build_file)

Au-delà du chemin de classe codé en dur, cette solution compilera automatiquement la solution étudiante et mon code de test JUnit, exécutera le test et imprimera les résultats dans un fichier. À ce stade, tout ce que j'ai à faire est de parcourir le fichier à la recherche des noms des étudiants et de leur rapport de test avant de pouvoir évaluer une note.

Extensions futures

Bien que le nouveau processus soit des années-lumière plus rapide que n'importe quelle notation que j'avais faite le semestre dernier, il y a encore des améliorations qui peuvent être apportées. Par exemple, il est possible d'automatiser le téléchargement des solutions des étudiants. Enfer, il est probablement même possible de programmer ce processus sur un serveur qui m'envoie par e-mail les résultats des tests à la date limite.

D'un autre côté, il pourrait être agréable de créer un rapport de test qui me donne juste les notes, donc je ne prends aucune sorte de charge cognitive pour traduire les cas de test en notes. Si c'est possible, il est probablement possible d'automatiser également le téléchargement des notes.

De bout en bout, nous aurions un système qui automatiserait complètement les notes des étudiants. Je n'aurais pas besoin de prendre le temps d'évaluer les notes. Au lieu de cela, je pourrais me concentrer sur ce qui m'importe, c'est-à-dire les commentaires des étudiants. Après tout, les notes sont en quelque sorte des mesures arbitraires. Les commentaires sont ce qui aide les élèves à grandir.

De plus, sans la charge cognitive de la notation, je serais probablement en mesure de créer un meilleur matériel de cours, de tenir de meilleures heures de bureau et d'offrir un meilleur soutien par e-mail. Ce serait le rêve !

Inconvénients

Récemment, je parlais à un ami de ce que j'avais fait pour automatiser ma notation, et il m'a posé une excellente question :

Si vous automatisez tout, comment allez-vous détecter le plagiat ?

Amigo, 2019

Et pour être honnête, ce n'est pas quelque chose auquel j'avais pensé. Bien sûr, à ce stade, ce n'est pas quelque chose dont je dois m'inquiéter. Après tout, j'examine chaque solution à des fins de retour d'information, je devrais donc être en mesure de détecter le plagiat.

Mais, il peut être amusant d'étendre la solution actuelle pour détecter le plagiat localement. En d'autres termes, je pourrais enregistrer toutes les solutions et les comparer les unes aux autres au fur et à mesure. Cela pourrait être amusant !

Cela dit, je n'ai jamais été un glouton pour la punition. Mes valeurs fondamentales sont basées sur la confiance, j'ai donc tendance à offrir ces mêmes valeurs aux étudiants. Si je ne soupçonne aucune tricherie, je ne vais pas partir à sa recherche. Ma confiance est la leur à perdre.

La puissance de l'automatisation

De temps en temps, je vois un mème se moquer des développeurs qui préfèrent prendre une heure pour écrire un script pour automatiser une tâche plutôt que de passer cinq minutes à faire cette tâche, et j'en suis tellement coupable. Cela dit, je ne pense pas que ma quête d'automatisation soit une mauvaise chose. Après tout, je partage toujours mes solutions avec le public.

Par exemple, vous êtes libre de consulter tout le code de test JUnit que j'utilise pour automatiser la notation de mon cours CSE 1223. Par exemple, le dossier Projects contient tous les scripts de test JUnit. Pendant ce temps, j'ai récemment déplacé le script Python vers son propre référentiel. N'hésitez pas à regarder autour de vous et à emprunter une partie de mon travail pour votre propre bénéfice. C'est pourquoi je fais ce que je fais !

De plus, je dois mentionner que le script de notation Python a subi de nombreuses modifications depuis que j'ai écrit cet article. Par exemple, il vide désormais toutes les notes dans un fichier JSON, ce qui me permet d'imbriquer des parties du fichier dans un IDE, ce qui facilite l'analyse. Avec l'amélioration de JSON, je suis en mesure d'avoir une idée de haut niveau de qui a bien fait et qui n'a pas fait, ce que j'utilise pour noter successivement des devoirs de notation similaires.

Si vous connaissez des enseignants qui pourraient être intéressés par la notation automatisée, pourquoi ne pas leur transmettre cet article. Je suis sûr qu'ils apprécieraient ! En tout cas, merci d'avoir pris le temps de lire cet article.


Balise Java