diff --git a/.DS_Store b/.DS_Store index 04a508e6f..ad8edd258 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 2cba99d87..6026fc595 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ bin include lib .Python -tests/ .envrc -__pycache__ \ No newline at end of file +__pycache__ +.coverage +.DS_Store \ No newline at end of file diff --git a/Locust_Test_Report.html b/Locust_Test_Report.html new file mode 100644 index 000000000..43c1411d3 --- /dev/null +++ b/Locust_Test_Report.html @@ -0,0 +1,156 @@ + + + +
+ + + + + + +virtualenv .. This will then set up a a virtual python environment within that directory.
+### Lancement de l’application
- - Next, type source bin/activate. You should see that your command prompt has changed to the name of the folder. This means that you can install packages in here without affecting affecting files outside. To deactivate, type deactivate
+Pour démarrer l’application Flask localement, tapez :
- - Rather than hunting around for the packages you need, you can install in one step. Type pip install -r requirements.txt. This will install all the packages listed in the respective file. If you install a package, make sure others know by updating the requirements.txt file. An easy way to do this is pip freeze > requirements.txt
+flask --app server.py run -p 5000
- - Flask requires that you set an environmental variable to the python file. However you do that, you'll want to set the file to be server.py. Check [here](https://flask.palletsprojects.com/en/1.1.x/quickstart/#a-minimal-application) for more details
- - You should now be ready to test the application. In the directory, type either flask run or python -m flask run. The app should respond with an address you should be able to go to using your browser.
+L’application sera accessible ensuite à l’adresse :
+`http://127.0.0.1:5000`
-4. Current Setup
+---
- The app is powered by [JSON files](https://www.tutorialspoint.com/json/json_quick_guide.htm). This is to get around having a DB until we actually need one. The main ones are:
-
- * competitions.json - list of competitions
- * clubs.json - list of clubs with relevant information. You can look here to see what email addresses the app will accept for login.
+## 🧪 Tests automatisés
-5. Testing
+### Lancement des tests unitaires et d’intégration
- You are free to use whatever testing framework you like-the main thing is that you can show what tests you are using.
+Les tests sont organisés dans le dossier `tests/`. Pour exécuter tous les tests, utilisez :
- We also like to show how well we're testing, so there's a module called
- [coverage](https://coverage.readthedocs.io/en/coverage-5.1/) you should add to your project.
+coverage run -m pytest
+
+Cela lance tous les tests tout en mesurant la couverture du code.
+
+### Visualiser le rapport de couverture
+
+Pour obtenir un rapport détaillé de la couverture de code :
+
+coverage report -m
+
+
+L’objectif est d’avoir un taux minimum de 60 % de couverture, mais ici la couverture est à 100 % sur `server.py`.
+
+---
+
+## 🚀 Tests de performance avec Locust
+
+### Description
+
+Locust simule des utilisateurs réels pour tester la performance sous charge. Ici, 6 utilisateurs effectuent les actions de consultation et réservation.
+
+### Lancement des tests Locust
+
+Dans un nouveau terminal, lancez Locust avec :
+
+locust -f locustfile.py --host=http://localhost:5000 --users 6 --spawn-rate 1 --run-time 30s
+
+
+Ensuite, ouvrez un navigateur à l’adresse :
+`http://localhost:8089`
+
+Cliquez sur start avec 6 utilisateurs pour commencer les tests.
+
+### Résultat attendu
+
+Les temps de réponse doivent être :
+- Inférieurs à 5 secondes pour le chargement des pages
+- Inférieurs à 2 secondes pour les achats de places
+
+Notre rapport `Locust_Test_Report.html` contient les résultats détaillés.
+
+---
+
+## Structure du projet
+
+python_testing/
+├── server.py # Application Flask principale
+├── tests/
+│ ├── test_unit.py # Tests unitaires
+│ ├── test_integration.py # Tests d’intégration
+│ └── conftest.py # Configuration pytest
+├── locustfile.py # Scénarios de tests de performance Locust
+├── Locust_Test_Report.html # Rapport de test de performance généré par Locust
+└── README.md # Ce fichier
diff --git a/clubs.json b/clubs.json
index 1d7ad1ffe..5184686ee 100644
--- a/clubs.json
+++ b/clubs.json
@@ -1,16 +1,19 @@
-{"clubs":[
+{
+ "clubs": [
{
- "name":"Simply Lift",
- "email":"john@simplylift.co",
- "points":"13"
+ "name": "Simply Lift",
+ "email": "john@simplylift.co",
+ "points": "13"
},
{
- "name":"Iron Temple",
- "email": "admin@irontemple.com",
- "points":"4"
+ "name": "Iron Temple",
+ "email": "admin@irontemple.com",
+ "points": "11"
},
- { "name":"She Lifts",
- "email": "kate@shelifts.co.uk",
- "points":"12"
+ {
+ "name": "She Lifts",
+ "email": "kate@shelifts.co.uk",
+ "points": "22"
}
-]}
\ No newline at end of file
+ ]
+}
\ No newline at end of file
diff --git a/competitions.json b/competitions.json
index 039fc61bd..7a7b37fdf 100644
--- a/competitions.json
+++ b/competitions.json
@@ -1,14 +1,24 @@
{
- "competitions": [
- {
- "name": "Spring Festival",
- "date": "2020-03-27 10:00:00",
- "numberOfPlaces": "25"
- },
- {
- "name": "Fall Classic",
- "date": "2020-10-22 13:30:00",
- "numberOfPlaces": "13"
- }
- ]
+ "competitions": [
+ {
+ "name": "Spring Festival",
+ "date": "2020-03-27 10:00:00",
+ "numberOfPlaces": "25"
+ },
+ {
+ "name": "Fall Classic",
+ "date": "2020-10-22 13:30:00",
+ "numberOfPlaces": "13"
+ },
+ {
+ "name": "Winter Gala",
+ "date": "2025-12-15 09:00:00",
+ "numberOfPlaces": "30"
+ },
+ {
+ "name": "New Year's Championship",
+ "date": "2026-01-02 11:00:00",
+ "numberOfPlaces": "10"
+ }
+ ]
}
\ No newline at end of file
diff --git a/locustfile.py b/locustfile.py
new file mode 100644
index 000000000..f15287525
--- /dev/null
+++ b/locustfile.py
@@ -0,0 +1,24 @@
+from locust import HttpUser, task, between
+
+class ClubSecretary(HttpUser):
+ wait_time = between(1, 3) # Pause 1-3s entre actions
+
+ @task
+ def visit_welcome(self):
+ self.client.get("/") # Page d'accueil
+
+ @task
+ def book_page(self):
+ self.client.get("/book/Winter Gala/Simply Lift") # Page réservation
+
+ @task
+ def points_page(self):
+ self.client.get("/points") # Tableau points
+
+ @task
+ def purchase_places(self):
+ self.client.post("/purchasePlaces", {
+ "competition": "Winter Gala",
+ "club": "Simply Lift",
+ "places": "1"
+ })
\ No newline at end of file
diff --git a/server.py b/server.py
index 4084baeac..d4c57b6c0 100644
--- a/server.py
+++ b/server.py
@@ -1,5 +1,7 @@
import json
from flask import Flask,render_template,request,redirect,flash,url_for
+from datetime import datetime
+
def loadClubs():
@@ -8,52 +10,129 @@ def loadClubs():
return listOfClubs
+
+
def loadCompetitions():
with open('competitions.json') as comps:
listOfCompetitions = json.load(comps)['competitions']
return listOfCompetitions
+
+def saveClubs(clubs_list):
+ """Sauvegarde la liste des clubs mise à jour dans clubs.json"""
+ with open('clubs.json', 'w') as f:
+ json.dump({'clubs': clubs_list}, f, indent=2)
+
+
+
+def saveCompetitions(competitions_list):
+ """Sauvegarde la liste des compétitions mise à jour dans competitions.json"""
+ with open('competitions.json', 'w') as f:
+ json.dump({'competitions': competitions_list}, f, indent=2)
+
+
+
+
app = Flask(__name__)
app.secret_key = 'something_special'
-competitions = loadCompetitions()
-clubs = loadClubs()
+
@app.route('/')
def index():
return render_template('index.html')
+
+
@app.route('/showSummary',methods=['POST'])
def showSummary():
+ clubs = loadClubs() # Recharge pour état actuel
club = [club for club in clubs if club['email'] == request.form['email']][0]
+ competitions = loadCompetitions() # Recharge pour état actuel
return render_template('welcome.html',club=club,competitions=competitions)
@app.route('/book/