La plateforme mooctest est conçue pour fournir des services d'évaluation automatisée pour les exercices et examens de programmation. Elle permet aux enseignants de gérer les processsus d'examen et aux étudiants de pratiquer librement. Le système prend en charge plusieurs langages, notamment Java (tests de couverture et programmation pilotée par les tests), Python (programmation statistique), C++, JMeter (tests de performance) et les tests d'applications Android.
Présentation du projet
Le projet mooctest a été lancé en août 2014 avec une équipe initiale de deux développeurs. Les étapes clés incluent la mise en place de la gestion des examens fin 2014, l'intégration avec des cours MOOC en 2015 et l'expansion vers de nouvelles matières comme les tests Android. La plateforme a été adoptée par de nombreuses universités pour les cours de tests logiciels.
Architecture teechnique
Le système est basé sur le framework Java Play 1.2.7, choisi pour sa simplicité et sa faible configuration par rapport à Struts ou Spring. La structure du projet suit une approche MVC avec des couches bien définies.
Structure des répertoires
Les composants principaux incluent :
- Le dossier
conf/contenant les configurations de l'application, les routes et les fichiers de messages pour l'internationalisation. - Le dossier
app/divisé en sous-dossiers pour les contrôleurs, les gestionnaires métier, les modèles de base de données et les utilitaires. - Le dossier
public/pour les ressources front-end comme CSS, JavaScript et images.
Base de données et ORM
Le système utilise MySQL avec JPA pour la persistance des objets. Les modèles sont définis à l'aide d'annotations JPA. Par exemple, un modèle pour un examen pourrait être défini ainsi :
import javax.persistence.*;
import play.db.jpa.Model;
@Entity
@Table(name="evaluation")
public class Evaluation extends Model {
@Column(name="titre")
private String titre;
@ManyToOne
@JoinColumn(name="formateur_id", referencedColumnName="id")
private Formateur formateur;
public String getTitre() { return titre; }
public void setTitre(String titre) { this.titre = titre; }
public Formateur getFormateur() { return formateur; }
public void setFormateur(Formateur formateur) { this.formateur = formateur; }
}
Pour éviter la duplication du code d'accès aux données, une classe générique DAO est utilisée :
public abstract class DaoGenerique<t extends="" pk="" serializable=""> {
private Class<t> typeClasse;
public DaoGenerique() {
typeClasse = (Class<t>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
public T chercherParId(PK id) {
return (T) JPA.em().find(typeClasse, id);
}
public List<t> chercherParColonne(String nomColonne, Object valeur) {
String[] colonnes = new String[1];
colonnes[0] = nomColonne;
Object[] valeurs = new Object[1];
valeurs[0] = valeur;
return chercherParColonnes(colonnes, valeurs);
}
public List<t> chercherParColonnes(String[] nomsColonnes, Object[] valeurs) {
String requete = "select e from " + typeClasse.getName() + " e where ";
for (int i = 0; i < nomsColonnes.length; i++) {
requete += "e." + nomsColonnes[i] + " = '" + valeurs[i].toString() + "'";
if (i < nomsColonnes.length - 1) requete += " and ";
}
return (List<t>) JPA.em().createQuery(requete).getResultList();
}
}</t></t></t></t></t></t>
Les DAO spécifiques héritent de cette classe, par exemple :
public class EvaluationDao extends DaoGenerique<evaluation long=""> {
public Evaluation chercherParFormateurEtTitre(long formateurId, String titre) {
String[] colonnes = {"formateur.id", "titre"};
Object[] valeurs = {formateurId, titre};
List<evaluation> liste = chercherParColonnes(colonnes, valeurs);
return (liste != null && !liste.isEmpty()) ? liste.get(0) : null;
}
}</evaluation></evaluation>
Cadre MVC et configuration des routes
Le framework Play utilise un modèle MVC où les contrôleurs gèrent les requêtes HTTP. Les routes sont définies dans un fichier de configuration pour mapper les URLs aux actions des contrôleurs. Par exemple :
# Pages publiques
GET / Application.index
POST /connexion ConnexionController.connecter
# Modules pour les rôles
/enseignant/{action} EnseignantController.{action}
/enseignant/examens/{action} EnseignantExamenController.{action}
Les contrôleurs sont organisés par rôles (administrateur, enseignant, étudiant) et appellent des couches métier, qui à leur tour utilisent les DAO pour l'accès aux données.
Support multilingue
Le système prend en charge plusieurs langues à l'aide de fichiers de messages. Une approche d'intercepteurs est utilisée pour définir la langue de l'application :
public class Application extends Controller {
private static final String LANGUE_PAR_DEFAUT = "fr_FR";
@Before
static void definirLangueGlobale() {
String langue = SessionUtil.obtenirLangue(session);
if (langue == null) {
List<string> languesNavigateur = request.acceptLanguage();
for (String lang : languesNavigateur) {
if (lang.contains("fr")) {
lang = LANGUE_PAR_DEFAUT;
}
if (Play.langs.contains(lang)) {
langue = lang;
break;
}
}
if (langue == null) langue = LANGUE_PAR_DEFAUT;
SessionUtil.stockerLangue(session, langue);
}
Lang.set(langue);
}
}</string>
Les textes de l'interface sont gérés via des clés dans les fichiers de messages, et un script inline fournit les traductions aux fichiers JavaScript.
Gestion des emails et tâches planifiées
Pour envoyer des emails, le système intègre un service EDM avec configuraton DNS et interface SMTP. Une file d'attente d'emails est implémentée pour gérer les échecs et les retentatives :
@Entity
@Table(name="courriel")
public class Courriel extends Model {
@Column(name="destinataire")
private String destinataire;
@Column(name="objet")
private String objet;
@Column(name="contenu")
private String contenu;
@Column(name="tentatives")
private Integer tentatives;
public Courriel() {
this.tentatives = 0;
}
}
Les tâches planifiées utilisent les jobs du framework Play pour traiter la file d'attente. Par exemple, un job pour envoyer des emails immédiats pourrait être :
public class JobEmailImmediat extends Job {
private EmailDao courrielDao = new EmailDao();
private String destinataire;
private String objet;
private String contenu;
public void doJob() {
try {
UtilitaireEmail.envoyer(destinataire, objet, contenu);
} catch (Exception e) {
Courriel echec = new Courriel();
echec.setDestinataire(destinataire);
echec.setObjet(objet);
echec.setContenu(contenu);
echec.setTentatives(1);
courrielDao.sauvegarder(echec);
}
}
}
Un job périodique traite la file d'attente pour réessayer l'envoi des emails échoués.
Composants front-end
Le front-end utilise des bibliothèques comme Highcharts pour les graphiques dynamiques et jQuery pour l'interactivité. Des composants réutilisables ont été développés, tels qu'un plugin de pagination et un sélecteur d'établissements scolaires, avec support pour les requêtes AJAX et l'internationalisation.