Tests YAML jury BUT: amélioration code test + yaml GEII Lyon ok
This commit is contained in:
parent
00c09b1eb8
commit
dd93d952d7
@ -170,7 +170,7 @@ class DecisionsProposees:
|
|||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"""<{self.__class__.__name__} valid={self.code_valide
|
return f"""<{self.__class__.__name__} valid={self.code_valide
|
||||||
} codes={self.codes} explanation={self.explanation}"""
|
} codes={self.codes} explanation={self.explanation}>"""
|
||||||
|
|
||||||
|
|
||||||
class DecisionsProposeesAnnee(DecisionsProposees):
|
class DecisionsProposeesAnnee(DecisionsProposees):
|
||||||
@ -732,7 +732,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
if self.formsemestre_pair is not None:
|
if self.formsemestre_pair is not None:
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre_pair.id)
|
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre_pair.id)
|
||||||
|
|
||||||
def record_all(self):
|
def record_all(self, no_overwrite: bool = True):
|
||||||
"""Enregistre les codes qui n'ont pas été spécifiés par le formulaire,
|
"""Enregistre les codes qui n'ont pas été spécifiés par le formulaire,
|
||||||
et sont donc en mode "automatique"
|
et sont donc en mode "automatique"
|
||||||
"""
|
"""
|
||||||
@ -746,7 +746,8 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
# rappel: le code par défaut est en tête
|
# rappel: le code par défaut est en tête
|
||||||
code = dec.codes[0] if dec.codes else None
|
code = dec.codes[0] if dec.codes else None
|
||||||
# enregistre le code jury seulement s'il n'y a pas déjà de code
|
# enregistre le code jury seulement s'il n'y a pas déjà de code
|
||||||
dec.record(code, no_overwrite=True)
|
# (no_overwrite=True) sauf en mode test yaml
|
||||||
|
dec.record(code, no_overwrite=no_overwrite)
|
||||||
|
|
||||||
def erase(self, only_one_sem=False):
|
def erase(self, only_one_sem=False):
|
||||||
"""Efface les décisions de jury de cet étudiant
|
"""Efface les décisions de jury de cet étudiant
|
||||||
@ -922,6 +923,10 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
else:
|
else:
|
||||||
self.codes.insert(1, self.code_valide)
|
self.codes.insert(1, self.code_valide)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"""<{self.__class__.__name__} rcue={self.rcue} valid={self.code_valide
|
||||||
|
} codes={self.codes} explanation={self.explanation}"""
|
||||||
|
|
||||||
def record(self, code: str, no_overwrite=False):
|
def record(self, code: str, no_overwrite=False):
|
||||||
"""Enregistre le code"""
|
"""Enregistre le code"""
|
||||||
if self.rcue is None:
|
if self.rcue is None:
|
||||||
@ -1073,7 +1078,7 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"""<{self.__class__.__name__} ue={self.ue.acronyme} valid={self.code_valide
|
return f"""<{self.__class__.__name__} ue={self.ue.acronyme} valid={self.code_valide
|
||||||
} codes={self.codes} explanation={self.explanation}"""
|
} codes={self.codes} explanation={self.explanation}>"""
|
||||||
|
|
||||||
def set_rcue(self, rcue: RegroupementCoherentUE):
|
def set_rcue(self, rcue: RegroupementCoherentUE):
|
||||||
"""Rattache cette UE à un RCUE. Cela peut modifier les codes
|
"""Rattache cette UE à un RCUE. Cela peut modifier les codes
|
||||||
|
@ -15,12 +15,17 @@ from app.scodoc import sco_cache
|
|||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_validation_auto_but(formsemestre: FormSemestre, only_adm=True) -> int:
|
def formsemestre_validation_auto_but(
|
||||||
|
formsemestre: FormSemestre, only_adm: bool = True, no_overwrite: bool = True
|
||||||
|
) -> int:
|
||||||
"""Calcul automatique des décisions de jury sur une année BUT.
|
"""Calcul automatique des décisions de jury sur une année BUT.
|
||||||
Normalement, only_adm est True et on n'enregistre que les décisions ADM (de droit).
|
Normalement, only_adm est True et on n'enregistre que les décisions ADM (de droit).
|
||||||
Si only_adm est faux, on enregistre la première décision proposée par ScoDoc
|
Si only_adm est faux, on enregistre la première décision proposée par ScoDoc
|
||||||
(mode à n'utiliser que pour les tests)
|
(mode à n'utiliser que pour les tests)
|
||||||
|
|
||||||
|
Si no_overwrite est vrai (défaut), ne ré-écrit jamais les codes déjà enregistrés
|
||||||
|
(utiliser faux pour certains tests)
|
||||||
|
|
||||||
Returns: nombre d'étudiants "admis"
|
Returns: nombre d'étudiants "admis"
|
||||||
"""
|
"""
|
||||||
if not formsemestre.formation.is_apc():
|
if not formsemestre.formation.is_apc():
|
||||||
@ -33,7 +38,7 @@ def formsemestre_validation_auto_but(formsemestre: FormSemestre, only_adm=True)
|
|||||||
if deca.admis: # année réussie
|
if deca.admis: # année réussie
|
||||||
nb_admis += 1
|
nb_admis += 1
|
||||||
if deca.admis or not only_adm:
|
if deca.admis or not only_adm:
|
||||||
deca.record_all()
|
deca.record_all(no_overwrite=no_overwrite)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return nb_admis
|
return nb_admis
|
||||||
|
@ -194,12 +194,11 @@ Etudiants:
|
|||||||
decision_jury: CMP
|
decision_jury: CMP
|
||||||
rcue:
|
rcue:
|
||||||
moy_rcue: 10.75
|
moy_rcue: 10.75
|
||||||
est_compensable: False
|
est_compensable: True
|
||||||
"UE12":
|
"UE12":
|
||||||
code_valide: CMP
|
code_valide: CMP # car validé en fin de S2
|
||||||
decision_jury: CMP
|
|
||||||
rcue:
|
rcue:
|
||||||
moy_rcue: 10.50
|
moy_rcue: 9.50 # la moyenne courante (et non enregistrée), donc pas 10.5
|
||||||
est_compensable: False
|
est_compensable: False
|
||||||
decision_annee: ADM
|
decision_annee: ADM
|
||||||
|
|
@ -35,7 +35,7 @@ def test_but_jury_GB(test_client):
|
|||||||
|
|
||||||
# Vérifie les deca de tous les semestres:
|
# Vérifie les deca de tous les semestres:
|
||||||
for formsemestre in FormSemestre.query:
|
for formsemestre in FormSemestre.query:
|
||||||
_check_deca(formsemestre)
|
yaml_setup.check_deca_fields(formsemestre)
|
||||||
|
|
||||||
# Saisie de toutes les décisions de jury
|
# Saisie de toutes les décisions de jury
|
||||||
for formsemestre in FormSemestre.query.order_by(FormSemestre.semestre_id):
|
for formsemestre in FormSemestre.query.order_by(FormSemestre.semestre_id):
|
||||||
@ -43,11 +43,11 @@ def test_but_jury_GB(test_client):
|
|||||||
|
|
||||||
# Vérifie résultats attendus:
|
# Vérifie résultats attendus:
|
||||||
S1: FormSemestre = FormSemestre.query.filter_by(titre="S1_SEE").first()
|
S1: FormSemestre = FormSemestre.query.filter_by(titre="S1_SEE").first()
|
||||||
_test_but_jury(S1, doc)
|
yaml_setup.test_but_jury(S1, doc)
|
||||||
S2: FormSemestre = FormSemestre.query.filter_by(titre="S2_SEE").first()
|
S2: FormSemestre = FormSemestre.query.filter_by(titre="S2_SEE").first()
|
||||||
_test_but_jury(S2, doc)
|
yaml_setup.test_but_jury(S2, doc)
|
||||||
S3: FormSemestre = FormSemestre.query.filter_by(titre="S3").first()
|
S3: FormSemestre = FormSemestre.query.filter_by(titre="S3").first()
|
||||||
_test_but_jury(S3, doc)
|
yaml_setup.test_but_jury(S3, doc)
|
||||||
# _test_but_jury(S1_redoublant, doc)
|
# _test_but_jury(S1_redoublant, doc)
|
||||||
|
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ def test_but_jury_GMP_lm(test_client):
|
|||||||
|
|
||||||
# Vérifie les deca de tous les semestres:
|
# Vérifie les deca de tous les semestres:
|
||||||
for formsemestre in formsemestres:
|
for formsemestre in formsemestres:
|
||||||
_check_deca(formsemestre)
|
yaml_setup.check_deca_fields(formsemestre)
|
||||||
|
|
||||||
# Saisie de toutes les décisions de jury qui ne le seraient pas déjà
|
# Saisie de toutes les décisions de jury qui ne le seraient pas déjà
|
||||||
for formsemestre in formsemestres:
|
for formsemestre in formsemestres:
|
||||||
@ -74,7 +74,7 @@ def test_but_jury_GMP_lm(test_client):
|
|||||||
|
|
||||||
# Vérifie résultats attendus:
|
# Vérifie résultats attendus:
|
||||||
for formsemestre in formsemestres:
|
for formsemestre in formsemestres:
|
||||||
_test_but_jury(formsemestre, doc)
|
yaml_setup.test_but_jury(formsemestre, doc)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow
|
@pytest.mark.slow
|
||||||
@ -90,71 +90,14 @@ def test_but_jury_GEII_lyon(test_client):
|
|||||||
FormSemestre.date_debut, FormSemestre.semestre_id
|
FormSemestre.date_debut, FormSemestre.semestre_id
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
# Vérifie les deca de tous les semestres:
|
# Vérifie les champs de DecisionsProposeesAnnee de tous les semestres:
|
||||||
for formsemestre in formsemestres:
|
for formsemestre in formsemestres:
|
||||||
_check_deca(formsemestre)
|
yaml_setup.check_deca_fields(formsemestre)
|
||||||
|
|
||||||
# Saisie de toutes les décisions de jury qui ne le seraient pas déjà
|
# Saisie de toutes les décisions de jury "automatiques"
|
||||||
|
# et vérification des résultats attendus:
|
||||||
for formsemestre in formsemestres:
|
for formsemestre in formsemestres:
|
||||||
formsemestre_validation_auto_but(formsemestre, only_adm=False)
|
formsemestre_validation_auto_but(
|
||||||
|
formsemestre, only_adm=False, no_overwrite=False
|
||||||
# Vérifie résultats attendus:
|
)
|
||||||
for formsemestre in formsemestres:
|
yaml_setup.test_but_jury(formsemestre, doc)
|
||||||
_test_but_jury(formsemestre, doc)
|
|
||||||
|
|
||||||
|
|
||||||
def _check_deca(formsemestre: FormSemestre, etud: Identite = None):
|
|
||||||
"""vérifie les champs principaux de l'instance de DecisionsProposeesAnnee"""
|
|
||||||
etud = etud or formsemestre.etuds.first()
|
|
||||||
assert etud # il faut au moins un étudiant dans le semestre
|
|
||||||
deca = DecisionsProposeesAnnee(etud, formsemestre)
|
|
||||||
assert deca.validation is None # pas encore de validation enregistrée
|
|
||||||
assert False is deca.recorded
|
|
||||||
assert deca.code_valide is None
|
|
||||||
if formsemestre.semestre_id % 2:
|
|
||||||
assert deca.formsemestre_impair == formsemestre
|
|
||||||
assert formsemestre.query_ues_parcours_etud(etud.id).all() == deca.ues_impair
|
|
||||||
else:
|
|
||||||
assert deca.formsemestre_pair == formsemestre
|
|
||||||
assert formsemestre.query_ues_parcours_etud(etud.id).all() == deca.ues_pair
|
|
||||||
assert deca.inscription_etat == scu.INSCRIT
|
|
||||||
assert deca.inscription_etat_impair == scu.INSCRIT
|
|
||||||
assert (deca.parcour is None) or (
|
|
||||||
deca.parcour.id in {p.id for p in formsemestre.parcours}
|
|
||||||
)
|
|
||||||
|
|
||||||
nb_ues = (
|
|
||||||
len(deca.formsemestre_pair.query_ues_parcours_etud(etud.id).all())
|
|
||||||
if deca.formsemestre_pair
|
|
||||||
else 0
|
|
||||||
)
|
|
||||||
nb_ues += (
|
|
||||||
len(deca.formsemestre_impair.query_ues_parcours_etud(etud.id).all())
|
|
||||||
if deca.formsemestre_impair
|
|
||||||
else 0
|
|
||||||
)
|
|
||||||
assert len(deca.decisions_ues) == nb_ues
|
|
||||||
|
|
||||||
nb_ues_un_sem = (
|
|
||||||
len(deca.formsemestre_impair.query_ues_parcours_etud(etud.id).all())
|
|
||||||
if deca.formsemestre_impair
|
|
||||||
else len(deca.formsemestre_pair.query_ues_parcours_etud(etud.id).all())
|
|
||||||
)
|
|
||||||
assert len(deca.niveaux_competences) == nb_ues_un_sem
|
|
||||||
assert deca.nb_competences == nb_ues_un_sem
|
|
||||||
|
|
||||||
|
|
||||||
def _test_but_jury(formsemestre: FormSemestre, doc: dict):
|
|
||||||
"""Test jurys
|
|
||||||
Vérifie les champs de DecisionsProposeesAnnee et UEs
|
|
||||||
"""
|
|
||||||
for etud in formsemestre.etuds:
|
|
||||||
deca = DecisionsProposeesAnnee(etud, formsemestre)
|
|
||||||
doc_formsemestre = doc["Etudiants"][etud.nom]["formsemestres"][
|
|
||||||
formsemestre.titre
|
|
||||||
]
|
|
||||||
assert doc_formsemestre
|
|
||||||
if "attendu" in doc_formsemestre:
|
|
||||||
if "deca" in doc_formsemestre["attendu"]:
|
|
||||||
deca_att = doc_formsemestre["attendu"]["deca"]
|
|
||||||
yaml_setup.compare_decisions_annee(deca, deca_att)
|
|
||||||
|
@ -1,7 +1,45 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Met en place une base pour les tests, à partir d'une description YAML
|
Met en place une base pour les tests unitaires, à partir d'une description
|
||||||
qui peut donner la formation, son ref. compétences, les formsemestres,
|
YAML qui peut donner la formation, son ref. compétences, les formsemestres,
|
||||||
les étudiants et leurs notes.
|
les étudiants et leurs notes et décisions de jury.
|
||||||
|
|
||||||
|
Le traitement est effectué dans l'ordre suivant:
|
||||||
|
|
||||||
|
setup_from_yaml()
|
||||||
|
|
||||||
|
- setup_but_formation():
|
||||||
|
- import de la formation (le test utilise une seule formation)
|
||||||
|
- associe_ues_et_parcours():
|
||||||
|
- crée les associations formation <-> référentiel de compétence
|
||||||
|
- setup_formsemestres()
|
||||||
|
- crée les formsemestres décrits dans le YAML
|
||||||
|
avec tous les modules du semestre ou des parcours si indiqués
|
||||||
|
et une évaluation dans chaque moduleimpl.
|
||||||
|
- inscrit_les_etudiants()
|
||||||
|
- inscrit et place dans les groupes de parcours
|
||||||
|
- note_les_modules()
|
||||||
|
- saisie de toutes les notes indiquées dans le YAML
|
||||||
|
|
||||||
|
check_deca_fields()
|
||||||
|
- vérifie les champs du deca (nb UE, compétences, ...) mais pas les décisions de jury.
|
||||||
|
|
||||||
|
formsemestre_validation_auto_but(only_adm=False)
|
||||||
|
- enregistre toutes les décisions "par défaut" proposées (pas seulement les ADM)
|
||||||
|
|
||||||
|
test_but_jury()
|
||||||
|
- compare décisions attendues indiquées dans le YAML avec celles de ScoDoc
|
||||||
|
et enregistre immédiatement après la décision manuelle indiquée par `decision_jury`
|
||||||
|
dans le YAML.
|
||||||
|
|
||||||
|
|
||||||
|
Les tests unitaires associés sont généralement lents (construction de la base),
|
||||||
|
et donc marqués par `@pytest.mark.slow`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@ -352,7 +390,7 @@ def _check_decisions_ues(
|
|||||||
decisions_ues: dict[int, DecisionsProposeesUE], decisions_ues_att: dict[str:dict]
|
decisions_ues: dict[int, DecisionsProposeesUE], decisions_ues_att: dict[str:dict]
|
||||||
):
|
):
|
||||||
"""Vérifie les décisions d'UE
|
"""Vérifie les décisions d'UE
|
||||||
pui enregistre décision manuelle si indiquée.
|
puis enregistre décision manuelle si indiquée dans le YAML.
|
||||||
"""
|
"""
|
||||||
for acronyme, dec_ue_att in decisions_ues_att.items():
|
for acronyme, dec_ue_att in decisions_ues_att.items():
|
||||||
# retrouve l'UE
|
# retrouve l'UE
|
||||||
@ -410,11 +448,18 @@ def _check_decisions_rcues(
|
|||||||
dec_rcue.rcue.est_compensable()
|
dec_rcue.rcue.est_compensable()
|
||||||
== dec_rcue_att["rcue"]["est_compensable"]
|
== dec_rcue_att["rcue"]["est_compensable"]
|
||||||
)
|
)
|
||||||
|
# Force décision de jury:
|
||||||
|
code_manuel = dec_rcue_att.get("decision_jury")
|
||||||
|
if code_manuel is not None:
|
||||||
|
assert code_manuel in dec_rcue.codes
|
||||||
|
dec_rcue.record(code_manuel)
|
||||||
|
|
||||||
|
|
||||||
def compare_decisions_annee(deca: DecisionsProposeesAnnee, deca_att: dict):
|
def compare_decisions_annee(deca: DecisionsProposeesAnnee, deca_att: dict):
|
||||||
"""Vérifie que les résultats de jury calculés sont ceux attendus,
|
"""Vérifie que les résultats de jury calculés pour l'année, les RCUEs et les UEs
|
||||||
|
sont ceux attendus,
|
||||||
puis enregistre les décisions manuelles indiquées dans le YAML.
|
puis enregistre les décisions manuelles indiquées dans le YAML.
|
||||||
|
|
||||||
deca est le résultat calculé par ScoDoc
|
deca est le résultat calculé par ScoDoc
|
||||||
deca_att est un dict lu du YAML
|
deca_att est un dict lu du YAML
|
||||||
"""
|
"""
|
||||||
@ -435,3 +480,70 @@ def compare_decisions_annee(deca: DecisionsProposeesAnnee, deca_att: dict):
|
|||||||
_check_decisions_rcues(
|
_check_decisions_rcues(
|
||||||
deca.decisions_rcue_by_niveau.values(), deca_att["decisions_rcues"]
|
deca.decisions_rcue_by_niveau.values(), deca_att["decisions_rcues"]
|
||||||
)
|
)
|
||||||
|
# Force décision de jury:
|
||||||
|
code_manuel = deca_att.get("decision_jury")
|
||||||
|
if code_manuel is not None:
|
||||||
|
assert code_manuel in deca.codes
|
||||||
|
deca.record(code_manuel)
|
||||||
|
assert deca.recorded
|
||||||
|
|
||||||
|
|
||||||
|
def check_deca_fields(formsemestre: FormSemestre, etud: Identite = None):
|
||||||
|
"""Vérifie les champs principaux (inscription, nb UE, nb compétences)
|
||||||
|
de l'instance de DecisionsProposeesAnnee.
|
||||||
|
Ne vérifie pas les décisions de jury proprement dites.
|
||||||
|
Si etud n'est pas spécifié, prend le premier inscrit trouvé dans le semestre.
|
||||||
|
"""
|
||||||
|
etud = etud or formsemestre.etuds.first()
|
||||||
|
assert etud # il faut au moins un étudiant dans le semestre
|
||||||
|
deca = DecisionsProposeesAnnee(etud, formsemestre)
|
||||||
|
assert deca.validation is None # pas encore de validation enregistrée
|
||||||
|
assert False is deca.recorded
|
||||||
|
assert deca.code_valide is None
|
||||||
|
if formsemestre.semestre_id % 2:
|
||||||
|
assert deca.formsemestre_impair == formsemestre
|
||||||
|
assert formsemestre.query_ues_parcours_etud(etud.id).all() == deca.ues_impair
|
||||||
|
else:
|
||||||
|
assert deca.formsemestre_pair == formsemestre
|
||||||
|
assert formsemestre.query_ues_parcours_etud(etud.id).all() == deca.ues_pair
|
||||||
|
assert deca.inscription_etat == scu.INSCRIT
|
||||||
|
assert deca.inscription_etat_impair == scu.INSCRIT
|
||||||
|
assert (deca.parcour is None) or (
|
||||||
|
deca.parcour.id in {p.id for p in formsemestre.parcours}
|
||||||
|
)
|
||||||
|
|
||||||
|
nb_ues = (
|
||||||
|
len(deca.formsemestre_pair.query_ues_parcours_etud(etud.id).all())
|
||||||
|
if deca.formsemestre_pair
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
nb_ues += (
|
||||||
|
len(deca.formsemestre_impair.query_ues_parcours_etud(etud.id).all())
|
||||||
|
if deca.formsemestre_impair
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
assert len(deca.decisions_ues) == nb_ues
|
||||||
|
|
||||||
|
nb_ues_un_sem = (
|
||||||
|
len(deca.formsemestre_impair.query_ues_parcours_etud(etud.id).all())
|
||||||
|
if deca.formsemestre_impair
|
||||||
|
else len(deca.formsemestre_pair.query_ues_parcours_etud(etud.id).all())
|
||||||
|
)
|
||||||
|
assert len(deca.niveaux_competences) == nb_ues_un_sem
|
||||||
|
assert deca.nb_competences == nb_ues_un_sem
|
||||||
|
|
||||||
|
|
||||||
|
def test_but_jury(formsemestre: FormSemestre, doc: dict):
|
||||||
|
"""Test jurys BUT
|
||||||
|
Vérifie les champs de DecisionsProposeesAnnee et UEs
|
||||||
|
"""
|
||||||
|
for etud in formsemestre.etuds:
|
||||||
|
deca = DecisionsProposeesAnnee(etud, formsemestre)
|
||||||
|
doc_formsemestre = doc["Etudiants"][etud.nom]["formsemestres"][
|
||||||
|
formsemestre.titre
|
||||||
|
]
|
||||||
|
assert doc_formsemestre
|
||||||
|
if "attendu" in doc_formsemestre:
|
||||||
|
if "deca" in doc_formsemestre["attendu"]:
|
||||||
|
deca_att = doc_formsemestre["attendu"]["deca"]
|
||||||
|
compare_decisions_annee(deca, deca_att)
|
||||||
|
Loading…
Reference in New Issue
Block a user