WIP: Nouveaux tests unitaires pour les cursus BUT

This commit is contained in:
Emmanuel Viennet 2022-12-07 23:04:11 +01:00 committed by iziram
parent 60fa12df81
commit 930a96b984
5 changed files with 343 additions and 1 deletions

View File

@ -83,6 +83,14 @@ class Identite(db.Model):
args = make_etud_args(etudid=etudid, code_nip=code_nip) args = make_etud_args(etudid=etudid, code_nip=code_nip)
return Identite.query.filter_by(**args).first_or_404() return Identite.query.filter_by(**args).first_or_404()
@classmethod
def create_etud(cls, **args):
"Crée un étudiant, avec admission et adresse vides."
etud: Identite = cls(**args)
etud.adresses.append(Adresse())
etud.admission.append(Admission())
return etud
@property @property
def civilite_str(self): def civilite_str(self):
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre, """returns 'M.' ou 'Mme' ou '' (pour le genre neutre,

View File

@ -70,6 +70,7 @@ python-docx==0.8.11
python-dotenv==0.20.0 python-dotenv==0.20.0
python-editor==1.0.4 python-editor==1.0.4
pytz==2022.1 pytz==2022.1
PyYAML==6.0
redis==4.2.2 redis==4.2.2
reportlab==3.6.9 reportlab==3.6.9
requests==2.27.1 requests==2.27.1

View File

@ -0,0 +1,61 @@
# Tests unitaires jury BUT
# Essais avec un BUT GB et deux parcours sur S1, S2, S3
FormationFilename: scodoc_formation_BUT_GB_v1.xml
ReferentielCompetencesFilename: but-GB-05012022-081625.xml
ReferentielCompetencesSpecialite: GB
FormSemestres:
# S1 et S2 avec les parcours séparés:
S1_SEE:
idx: 1
date_debut: 2021-09-01
date_fin: 2022-01-15
codes_parcours: ['SEE']
S1_BMB:
idx: 1
date_debut: 2021-09-01
date_fin: 2022-01-15
codes_parcours: ['BMB']
S2_SEE:
idx: 2
date_debut: 2022-01-15
date_fin: 2022-06-30
codes_parcours: ['SEE']
S2_BMB:
idx: 2
date_debut: 2022-01-15
date_fin: 2022-06-30
codes_parcours: ['BMB']
# S3 avec les deux parcours réunis:
S3:
idx: 3
date_debut: 2022-09-01
date_fin: 2023-01-15
codes_parcours: ['SEE', 'BMB']
Etudiants:
Aaaaa:
prenom: Étudiant
civilite: M
formsemestres:
S1_SEE:
parcours: SEE
notes_modules:
R1.01: 12
R1.SEE.11: 15
S2_SEE:
parcours: SEE
S3:
parcours: SEE
Bbbbb:
prenom: Étudiante
civilite: F
formsemestres:
S1_BMB:
parcours: BMB
S2_BMB:
parcours: BMB
S3:
parcours: BMB

272
tests/unit/test_but_jury.py Normal file
View File

@ -0,0 +1,272 @@
""" Test jury BUT avec parcours
"""
import datetime
import os
from pathlib import Path
from flask import current_app, g
import pytest
import yaml
import app
from app import db
from app.auth.models import User
from app.but.import_refcomp import orebut_import_refcomp
from app.models import (
ApcParcours,
ApcReferentielCompetences,
Evaluation,
Formation,
FormSemestre,
Identite,
Module,
ModuleImpl,
ModuleUECoef,
)
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_groups
from app.scodoc import sco_saisie_notes
from app.scodoc import sco_utils as scu
from config import TestConfig
from tests.conftest import RESOURCES_DIR
DEPT = TestConfig.DEPT_TEST
def setup_but_formation(doc: dict) -> Formation:
"""Importe la formation BUT, l'associe au référentiel de compétences.
Après cette fonction, on a une formation chargée, et associée à son ref. comp.
"""
refcomp_filename = doc["ReferentielCompetencesFilename"]
refcomp_specialite = doc["ReferentielCompetencesSpecialite"]
app.set_sco_dept(DEPT)
# Lecture fichier XML local:
with open(
os.path.join(RESOURCES_DIR, "formations", doc["FormationFilename"]),
encoding="utf-8",
) as f:
doc = f.read()
# --- Création de la formation
formation_id, _, _ = sco_formations.formation_import_xml(doc)
formation: Formation = Formation.query.get(formation_id)
assert formation
# --- Chargement Référentiel
if (
ApcReferentielCompetences.query.filter_by(
scodoc_orig_filename=refcomp_filename, dept_id=g.scodoc_dept_id
).first()
is None
):
# pas déjà chargé
filename = (
Path(current_app.config["SCODOC_DIR"])
/ "ressources/referentiels/but2022/competences"
/ refcomp_filename
)
with open(filename, encoding="utf-8") as f:
xml_data = f.read()
referentiel_competence = orebut_import_refcomp(
xml_data, dept_id=g.scodoc_dept_id, orig_filename=Path(filename).name
)
assert referentiel_competence
# --- Association au référentiel de compétences
referentiel_competence = ApcReferentielCompetences.query.filter_by(
specialite=refcomp_specialite
).first() # le recherche à nouveau (test)
assert referentiel_competence
formation.referentiel_competence_id = referentiel_competence.id
db.session.add(formation)
db.session.commit()
return formation
def _un_semestre(
formation: Formation,
parcours: list[ApcParcours],
semestre_id: int,
titre: str,
date_debut: str,
date_fin: str,
) -> FormSemestre:
"Création d'un formsemetre"
formsemestre = FormSemestre(
formation=formation,
parcours=parcours,
dept_id=g.scodoc_dept_id,
titre=titre,
semestre_id=semestre_id,
date_debut=date_debut,
date_fin=date_fin,
)
# set responsable (list)
a_user = User.query.first()
formsemestre.responsables = [a_user]
db.session.add(formsemestre)
# Ajoute pour chaque UE une ressource avec un coef vers cette UE
added_ressources = set()
for parcour in parcours + [None]:
for ue in formation.query_ues_parcour(parcour):
ressource = (
Module.query.filter_by(
formation=formation,
semestre_id=1,
module_type=scu.ModuleType.RESSOURCE,
)
.join(ModuleUECoef)
.filter_by(ue=ue)
.first()
)
if ressource is not None:
if ressource.id not in added_ressources:
modimpl = ModuleImpl(module=ressource, responsable_id=a_user.id)
db.session.add(modimpl)
formsemestre.modimpls.append(modimpl)
added_ressources.add(ressource.id)
# Ajoute la première SAE
sae = formation.modules.filter_by(
semestre_id=1, module_type=scu.ModuleType.SAE
).first()
modimpl = ModuleImpl(module=sae, responsable_id=a_user.id)
formsemestre.modimpls.append(modimpl)
# Crée une évaluation dans chaque module
create_evaluations(formsemestre)
# Partition par défaut:
db.session.commit()
partition_id = sco_groups.partition_create(
formsemestre.id, default=True, redirect=False
)
_ = sco_groups.create_group(partition_id, default=True)
# Partition de parcours:
formsemestre.setup_parcours_groups()
return formsemestre
def create_evaluations(formsemestre: FormSemestre):
"""Crée une évaluation dans chaque module du semestre"""
for modimpl in formsemestre.modimpls:
evaluation = Evaluation(
moduleimpl=modimpl,
jour=formsemestre.date_debut,
description=f"Exam {modimpl.module.titre}",
coefficient=1.0,
note_max=20.0,
numero=1,
)
db.session.add(evaluation)
# Affecte les mêmes poids que les coefs APC:
ue_coef_dict = modimpl.module.get_ue_coef_dict() # { ue_id : coef }
evaluation.set_ue_poids_dict(ue_coef_dict)
def note_les_modules(doc: dict):
"""Saisie les notes des étudiants"""
a_user = User.query.first()
for nom, infos in doc["Etudiants"].items():
etud: Identite = Identite.query.filter_by(nom=nom).first()
assert etud is not None
for titre, sem_infos in infos["formsemestres"].items():
formsemestre: FormSemestre = FormSemestre.query.filter_by(
titre=titre
).first()
assert formsemestre is not None
for code_module, note in sem_infos.get("notes_modules", {}).items():
modimpl = (
formsemestre.modimpls.join(Module)
.filter_by(code=code_module)
.first()
)
# le sem. doit avoir un module du code indiqué:
assert modimpl is not None
for evaluation in modimpl.evaluations:
# s'il y a plusieurs evals, affecte la même note à chacune
sco_saisie_notes.notes_add(
a_user,
evaluation.id,
[(etud.id, float(note))],
comment="note_les_modules",
)
def setup_but_formsemestres(formation: Formation, doc: str):
"""Création des formsemestres pour tester les parcours BUT"""
for titre, infos in doc["FormSemestres"].items():
parcours = []
for code_parcour in infos["codes_parcours"]:
parcour = formation.referentiel_competence.parcours.filter_by(
code=code_parcour
).first()
assert parcour is not None
parcours.append(parcour)
_ = _un_semestre(
formation,
parcours,
infos["idx"],
titre,
infos["date_debut"],
infos["date_fin"],
)
db.session.flush()
assert FormSemestre.query.count() == len(doc["FormSemestres"])
def inscrit_les_etudiants(formation: Formation, doc: dict):
"""Inscrit les étudiants dans chacun de leurs formsemestres"""
for nom, infos in doc["Etudiants"].items():
etud = Identite.create_etud(
dept_id=g.scodoc_dept_id,
nom=nom,
prenom=infos["prenom"],
civilite=infos["civilite"],
)
db.session.add(etud)
db.session.commit()
# L'inscrire à ses formsemestres
for titre, sem_infos in infos["formsemestres"].items():
formsemestre: FormSemestre = FormSemestre.query.filter_by(
titre=titre
).first()
assert formsemestre is not None
partition_parcours = formsemestre.partitions.filter_by(
partition_name=scu.PARTITION_PARCOURS
).first()
if partition_parcours is None:
group_ids = []
else:
group = partition_parcours.groups.filter_by(
group_name=sem_infos["parcours"]
).first()
assert group is not None # le groupe de parcours doit exister
group_ids = [group.id]
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
formsemestre.id,
etud.id,
group_ids=group_ids,
etat=scu.INSCRIT,
method="tests/unit/inscrit_les_etudiants",
)
# Met à jour les inscriptions:
for formsemestre in formation.formsemestres:
formsemestre.update_inscriptions_parcours_from_groups()
def test_but_jury(test_client):
"Test jurys BUT1, BUT2 avec parcours"
with open("tests/unit/cursus_but_gb.yaml", encoding="utf-8") as f:
doc = yaml.safe_load(f.read())
formation = setup_but_formation(doc)
setup_but_formsemestres(formation, doc)
inscrit_les_etudiants(formation, doc)
note_les_modules(doc)

View File

@ -155,7 +155,7 @@ def create_fake_etud(dept: Departement) -> Identite:
"""Créé un faux étudiant et l'insère dans la base.""" """Créé un faux étudiant et l'insère dans la base."""
civilite = random.choice(("M", "F", "X")) civilite = random.choice(("M", "F", "X"))
nom, prenom = nomprenom(civilite) nom, prenom = nomprenom(civilite)
etud: Identite = Identite( etud: Identite = Identite.create_etud(
civilite=civilite, nom=nom, prenom=prenom, dept_id=dept.id civilite=civilite, nom=nom, prenom=prenom, dept_id=dept.id
) )
db.session.add(etud) db.session.add(etud)