Afficher dans des onglets les graphes choisis
Avant-propos
os.path
Description
Robot/backups
UiMain.open_graph()
vide, c'est à dire la méthode open_graph()
dans la classe UiMain
, contenant un simple print()
provisoire pour vérifier.UiMain.__init__()
, l'événement sur actionOpen
(clic sur le bouton 'Ouvrir un graphe'), qui appellera cette méthode open_graph()
.
Main
, cliquer sur 'Ouvrir un graphe' pour vérifier : Le message du print()
s'affiche.open_graph()
:
Robot/backups
pour choisir un graphe (dossier portant le nom du graphe).NewGraph
) pour le modifier :
open_graph()
en lui passant comme argument le nom du nouveau graphe.
Vérification
TDD - Veuillez remplacer le contenu de test_main.py
par celui-ci :
from PyQt5.QtWidgets import QApplication, QTabBar
from PyQt5.QtCore import QSettings, QRect, QTimer, Qt
from PyQt5.QtTest import QTest
from pc.main import UiMain, NewGraph
import pytest
import sys
import os
import random
class Main(UiMain):
""" Classe dérivée de la fenêtre à tester. """
def __init__(self, normal, dockable):
super().__init__()
self.premier_passage = normal is not None
self.dockable = dockable
self.sauvage = normal is False
self.settings_ante = QSettings(f"{os.getcwd()}{os.sep}ante.conf", QSettings.IniFormat)
def moveEvent(self, ev):
if not self.dockable:
super().moveEvent(ev)
def resizeEvent(self, ev):
if not self.dockable:
super().resizeEvent(ev)
def closeEvent(self, ev):
if self.premier_passage:
""" Premier passage : Mémorisation des états. """
self.settings_ante.setValue('Geometrie', self.geometry())
self.settings_ante.setValue('dockNodes visible', self.dockNodes.isVisible())
self.settings_ante.setValue('dockNodes détaché', self.dockNodes.isFloating())
self.settings_ante.setValue('dockNodes géométrie', self.dockNodes.geometry())
self.settings_ante.setValue('dockParams visible', self.dockParams.isVisible())
self.settings_ante.setValue('dockParams détaché', self.dockParams.isFloating())
self.settings_ante.setValue('dockParams géométrie', self.dockParams.geometry())
if not self.sauvage:
""" Non exécuté en mode 'sauvage'. """
super().closeEvent(ev)
# @pytest.mark.skip() # Commenter pour tester
class TestMain:
g_appli = QApplication(sys.argv)
tempo = QTimer()
tempo.setSingleShot(True)
@staticmethod
@pytest.fixture(scope="session", autouse=True)
def cleanup(request):
""" Appel final pour effacer les fichiers temporaires créés par les tests. """
def remove_files():
test_folder = os.getcwd() + os.sep
backups_folder = test_folder.replace('tests', 'backups')
files = [f"{test_folder}ante.conf", f"{test_folder}params.conf"]
folders = ['Test creation de graphe', 'test_graph_1', 'test_graph_2', 'test_graph_3']
for file in files:
if os.path.isfile(file):
os.remove(file)
for folder in folders:
if os.path.isdir(backups_folder + folder):
os.rmdir(backups_folder + folder)
request.addfinalizer(remove_files)
@staticmethod
def get_rect(t_x, t_y, t_w, t_h):
""" Retourne un QRect aléatoire. """
return QRect(random.randint(*t_x), random.randint(*t_y), random.randint(*t_w), random.randint(*t_h))
@staticmethod
def open_window(normal=None, dockable=False):
win = Main(normal, dockable)
if normal is not None:
win.settings_ante.clear()
win.show()
return win
def connect(self, function):
try:
self.tempo.timeout.disconnect()
except TypeError:
pass
self.tempo.timeout.connect(function)
def fenetre_modif(self, normal):
win = self.open_window(normal)
win.setGeometry(self.get_rect((50, 800), (50, 800), (300, 800), (300, 800)))
self.connect(win.close)
self.tempo.start(300) # {x}ms : On laisse le temps à la fenêtre main de mémoriser ses états.
""" Boucle d'exécution. """
self.g_appli.exec()
def fenetre_assert(self):
def geometry(qrect):
return f"x = {qrect.x()}, y = {qrect.y()}, largeur = {qrect.width()}, hauteur = {qrect.height()}"
win = self.open_window()
self.connect(win.close)
self.tempo.start(50)
geometry_ante = win.settings_ante.value('Geometrie', QRect(200, 200, 800, 600))
geometry_now = win.geometry()
print(
"\n- La fenêtre doit apparaître avec la même géométrie (taille et position) "
"que lors de sa dernière fermeture."
f"\n- Géométrie à la dernière fermeture : {geometry(geometry_ante)}."
f"\n- Géométrie à l'ouverture actuelle : {geometry(geometry_now)}."
)
assert geometry_now == geometry_ante
""" Boucle d'exécution. """
self.g_appli.exec()
""" *************************************************************************************** """
""" LES TESTS """
""" *************************************************************************************** """
# @pytest.mark.skip() # Commenter pour tester
def test_ferme_normal(self):
""" Phase 1 : Affichage de la fenêtre, plusieurs déplacements et changements de taille, aléatoires.
Fermeture de la fenêtre, mémorisation de sa géométrie. """
self.fenetre_modif(normal=True) # True = Fermeture normale.
""" Phase 2: Réouverture de la fenêtre, lecture de sa géométrie, comparaison. """
self.fenetre_assert()
# @pytest.mark.skip() # Commenter pour tester
def test_ferme_sauvage(self):
self.fenetre_modif(normal=False) # False = Fermeture sauvage.
self.fenetre_assert()
# @pytest.mark.skip() # Commenter pour tester
def test_dock_state(self):
def update_dock():
self.connect(win.close)
self.tempo.start(500) # {x} ms : On laisse le temps à la fenêtre main de mémoriser ses états.
""" Modification des états des dockables, de façon aléatoire. """
for o_dock in [win.dockNodes, win.dockParams]:
x, y = win.x() + win.width() // 2, win.y() + win.height() // 2
o_dock.setFloating(random.choice([False, True]))
if o_dock.isFloating():
o_dock.setGeometry(self.get_rect((x - 50, x + 50), (y - 50, y + 50), (50, 400), (50, 400)))
else:
o_dock.setFixedWidth(random.randint(82, 200))
def state_dock():
self.connect(win.close) # Obligatoirement avant les asserts.
self.tempo.start(50)
""" Vérification des résultats. """
msg = 'DockNodes : Flottant (détaché).'
assert win.dockNodes.isFloating() == win.settings_ante.value('dockNodes détaché'), msg
res, qrect_now, qrect_ante = 0, win.dockNodes.geometry().getRect(), win.settings_ante.value('dockNodes géométrie').getRect()
if win.dockNodes.isFloating():
msg = 'DockNodes : Géométrie (x, y, w, h).'
for i in range(4):
""" Tolérence : on accepte 3 bonnes valeurs sur 4. """
res += qrect_now[i] == qrect_ante[i]
assert res > 2, msg
else:
msg = 'DockNodes : Largeur.' # 0=x, 1=y, 2=width, 3=height
assert qrect_now[2] == qrect_ante[2], msg # [2]=width
msg = 'DockParams : Flottant (détaché).'
assert win.dockParams.isFloating() == win.settings_ante.value('dockParams détaché'), msg
res, qrect_now, qrect_ante = 0, win.dockParams.geometry().getRect(), win.settings_ante.value('dockParams géométrie').getRect()
if win.dockParams.isFloating():
msg = 'DockParams : Géométrie (x, y, w, h).'
for i in range(4):
res += qrect_now[i] == qrect_ante[i]
assert res > 2, msg
else:
msg = 'DockParams : Largeur.'
assert qrect_now[2] == qrect_ante[2], msg
""" Phase 1 : Modification aléatoire des dockables (détaché ou attaché, position, taille, visibilité). """
win = self.open_window(normal=True)
update_dock()
""" Boucle d'exécution. Sortie lorsque la fenêtre se ferme. """
self.g_appli.exec()
""" Phase 2 : Ouverture fenêtre, lecture de l'état des dockables, assertions et fermeture. """
win = self.open_window()
state_dock()
""" Boucle d'exécution. Sortie lorsque la fenêtre se ferme. """
self.g_appli.exec()
# @pytest.mark.skip() # Commenter pour tester
def test_dock_visible(self):
def update_dock():
self.connect(win.close)
self.tempo.start(300) # {x} ms : On laisse le temps à la fenêtre main de mémoriser ses états.
""" Modification des visibilités, de façon aléatoire. """
for o_dock in [win.dockNodes, win.dockParams]:
o_dock.setVisible(random.choice([False, True]))
def state_dock():
self.connect(win.close)
self.tempo.start(50) # Obligatoirement avant les asserts.
""" Vérification des résultats. """
try:
msg = 'DockNodes : Visibilité.'
assert win.dockNodes.isVisible() == win.settings_ante.value('dockNodes visible'), msg
msg = 'DockParams : Visibilité.'
assert win.dockParams.isVisible() == win.settings_ante.value('dockParams visible'), msg
except (Exception,):
pass
for i in range(4):
""" Phase 1 : Modification aléatoire des dockables (détaché ou attaché, position, taille, visibilité). """
win = self.open_window(normal=True, dockable=True)
update_dock()
""" Boucle d'exécution. Sortie lorsque la fenêtre se ferme. """
self.g_appli.exec()
""" Phase 2 : Ouverture fenêtre, lecture de l'état des dockables, assertions et fermeture. """
win = self.open_window()
state_dock()
""" Boucle d'exécution. Sortie lorsque la fenêtre se ferme. """
self.g_appli.exec()
# @pytest.mark.skip()
def test_new_graph(self):
def write():
""" Écriture animée. """
new_graph.graph_name.setText(graph_name[:indx[0]])
indx[0] += 1
if indx[0] <= len(graph_name):
""" Écriture +1 caractère chaque xxx ms. """
self.tempo.start(100)
else:
""" Simulation de clic sur le bouton 'Ok'. """
self.connect(new_graph.accept)
self.tempo.start(1000)
graph_name = 'Test creation de graphe'
bk_folder = f"{os.getcwd()}{os.sep}..{os.sep}backups"
created_folder = bk_folder + os.sep + graph_name
win = self.open_window()
new_graph = NewGraph(win) # Type QDialog
indx = [0]
self.connect(write)
self.tempo.start()
new_graph.exec()
self.connect(win.close)
self.tempo.start(50) # Obligatoirement avant les asserts.
self.g_appli.exec()
""" Le dossier 'D:/Robot/backups/Test creation de graphe' doit avoir été créé. Vérification : """
assert os.path.isdir(created_folder), f"Le dossier '{created_folder}' n'a pas été créé."
# @pytest.mark.skip()
def test_open_graph(self):
""" - État initial : à la création, il n'y a aucun onglet. Plus tard, il pourrait y en avoir un ou plusieurs.
- Création de 3 dossiers supplémentaires dans /backups : /test_graph_1, /test_graph_2 et /test_graph_3.
- Pour chaque dossier, appel de la méthode open_graph() :
- On vérifie que le dernier ajouté est sélectionné.
- On vérifie que la fenêtre Main a 3 onglets de plus dans sa zône centrale.
- On vérifie qu'ils sont déplaçables à la souris par drag & drop.
- On vérifie également qu'ils possèdent la croix de fermeture.
- Tentative d'ajout de /test_graph_2, qui est déjà présent :
- On vérifie le rejet de doublons.
- On vérifie néanmoins qu'il est sélectionné.
- Fermeture des 3 onglets => Vérification.
- Suppression des 3 dossiers de /backups.
"""
""" Ouverture et géométrie de la fenêtre Main. """
win = self.open_window()
win.setGeometry(QRect(100, 100, 900, 400))
self.connect(win.close)
self.tempo.start(800) # Fermeture différée.
""" Fermeture éventuelle des onglets. """
for i in range(3):
win.close_graph(0)
""" Création et affichage des onglets de 3 graphes. """
for i, dir_name in enumerate(['test_graph_1', 'test_graph_2', 'test_graph_3']):
created_folder = os.path.abspath(f"{os.getcwd()}{os.sep}..{os.sep}backups{os.sep}{dir_name}")
os.makedirs(created_folder, exist_ok=True) # Création dossier. Si ce dossier existe, pas d'erreur renvoyée.
win.open_graph(dir_name)
print(win.tabGraphs.currentIndex(), i)
# assert win.tabGraphs.currentIndex() == nb_ante + i - 1, "L'onglet sélectionné n'est pas le dernier ajouté."
""" Asserts. """
nb_now = win.tabGraphs.count() # Le nombre d'onglets a augmenté de 3.
vrai = True
assert nb_now == 3, "L'ajout d'onglets a échoué."
assert win.tabGraphs.isMovable() == vrai, "Les onglets ne sont pas déplaçables à la souris."
assert win.tabGraphs.tabsClosable() == vrai, "Les onglets ne possèdent pas la croix de fermeture."
""" Tentative d'ouverture d'un graphe déjà ouvert. """
win.open_graph("test_graph_2")
assert nb_now == win.tabGraphs.count(), "La gestion des doublons ne fonctionne pas."
assert win.tabGraphs.currentIndex() == 1, "L'onglet sélectionné n'est pas celui du graphe choisi."
""" Boucle d'exécution. Sortie lorsque la fenêtre se ferme. """
self.g_appli.exec()
# @pytest.mark.skip()
def test_close_graph(self):
""" Ouverture et géométrie de la fenêtre Main. """
win = self.open_window()
win.setGeometry(QRect(200, 200, 900, 400))
self.connect(win.close)
self.tempo.start(800) # Fermeture différée.
""" Création et affichage des onglets de 3 graphes. """
for i, dir_name in enumerate(['test_graph_1', 'test_graph_2', 'test_graph_3']):
created_folder = os.path.abspath(f"{os.getcwd()}{os.sep}..{os.sep}backups{os.sep}{dir_name}")
os.makedirs(created_folder, exist_ok=True) # Création dossier. Si ce dossier existe, pas d'erreur renvoyée.
win.open_graph(dir_name)
""" Fermeture successive des 3 onglets. """
nb_ante = win.tabGraphs.count()
for i in range(1, 4):
close_button = win.tabGraphs.tabBar().tabButton(0, QTabBar.RightSide)
QTest.mouseClick(close_button, Qt.LeftButton, Qt.NoModifier)
nb_now = win.tabGraphs.count()
assert nb_now == nb_ante - i, "La fermeture des onglets a échoué."
""" Boucle d'exécution. Sortie lorsque la fenêtre se ferme. """
self.g_appli.exec()
Vérification : →
Bon courage et bon coding !
Snippets
Bonjour les codeurs !