Êtes-vous un testeur QA désireux de vous lancer dans les tests de performances sans avoir besoin d’une expertise approfondie en programmation ? Dans cet article, nous explorerons un moyen accessible aux non-programmeurs d'effectuer des tests de charge sur les API des applications cloud à l'aide de Python . Tests de charge sans avoir besoin d'un codage complexe : découvrez comment même les testeurs d'assurance qualité réguliers peuvent utiliser Python pour détecter des bogues graves et découvrir d'éventuels goulots d'étranglement en matière de performances.
Les tests de performances sont un aspect essentiel pour garantir que vos applications peuvent répondre aux demandes du monde réel. Je vais essayer d'expliquer mon approche et les scripts Python conçus pour tester en charge un service cloud gérant les navigateurs.
Scénario de test de charge Imaginez un service cloud chargé de gérer les profils de navigateur (navigateurs pour la mise au rebut Web). Les utilisateurs interagissent via l'API avec le service pour créer, démarrer, arrêter, supprimer, etc. des profils. Mon script Python simule ce scénario, appliquant une charge au service cloud en effectuant ces actions à plusieurs reprises.
# Dependencies import asyncio import httpx # Configuration API_HOST = 'https://cloud.io' API_KEY = 'qatest' API_HEADERS = { "x-cloud-api-token": API_KEY, "Content-Type": "application/json" } CYCLES_COUNT = 3 # Browser profile configuration data_start = { "proxy": "http://127.0.0.1:8080", "browser_settings": {"inactive_kill_timeout": 120} }
get_profiles
récupère les profils de navigateur existants du service, simulant un scénario dans lequel les utilisateurs demandent des informations. async def get_profiles(cl: httpx.AsyncClient): resp = await cl.get(f'{API_HOST}/profiles', params={'page_len': 10, 'page': 0}, headers=API_HEADERS) return resp.json()
async def start_profile(cl: httpx.AsyncClient, uuid): resp = await cl.post(f'{API_HOST}/profiles/{id}/start', json=data_start, headers=API_HEADERS) if error := resp.json().get('error'): print(f'Profile {id} not started with error {error}')
async def stop_profile(cl: httpx.AsyncClient, uuid): resp = await cl.post(f'{API_HOST}/profiles/{id}/stop', headers=API_HEADERS) if error := resp.json().get('error'): print(f'Profile {id} not stopped with error {error}') async def delete_profile(cl: httpx.AsyncClient, uuid): resp = await cl.delete(f'{API_HOST}/profiles/{id}', headers=API_HEADERS) if error := resp.json().get('error'): print(f'Profile {id} not stopped with error {error}')
for conn in cl._transport._pool.connections: if conn._connection._state.value != 1: continue print(f'Connection in progress: {conn}')
La fonction principale orchestre les cycles de tests de charge, en parcourant les profils et en exécutant des tâches asynchrones. Chaque cycle représente une interaction utilisateur simulée, créant, utilisant et supprimant des profils de navigateur.
async def main(): async with httpx.AsyncClient(timeout=httpx.Timeout(timeout=300)) as cl: for _ in range(CYCLES_COUNT): profiles = await get_profiles(cl) start_tasks = [asyncio.create_task(start_profile(cl, profile['id'])) for profile in profiles] await asyncio.gather(*start_tasks) active_browsers = await get_active_profiles(cl) stop_tasks = [asyncio.create_task(stop_profile(cl, active_browser['id'])) for active_browser in active_browsers['data']] await asyncio.gather(*stop_tasks) profiles = await get_profiles(cl) del_tasks = [asyncio.create_task(delete_profile(cl, profile['id'])) for profile in profiles] await asyncio.gather(*del_tasks) # Monitor active connections for insights into load impact
Ce script montre une base permettant aux responsables de l'assurance qualité d'adapter les scénarios de tests de charge à leurs applications. En personnalisant le nombre de cycles, en ajustant les interactions des utilisateurs et en modifiant le script pour l'adapter à des points de terminaison d'API spécifiques, les testeurs peuvent obtenir des informations précieuses sur les performances de leur application sous différentes charges. Ici, vous aurez besoin d'outils de surveillance essentiels pour obtenir des informations sur l'état du serveur, évaluer la charge du serveur et suivre l'utilisation des ressources et les journaux. Utilisez des outils tels que Grafana, Kibana, Prometheus, etc. pour une surveillance complète. De plus, surveillez de près les réponses reçues par votre script, garantissant ainsi une évaluation approfondie des performances de votre application. Cette approche est inestimable pour vos tests de charge efficaces et votre analyse des performances.
De plus, pour une simulation de charge plus réaliste, pensez à ouvrir des pages spécifiques dans votre navigateur. Bien que j'aie personnellement utilisé une page de démarrage dans mes navigateurs, vous pouvez également explorer des options telles que Pyppeteer ou Playwright pour ouvrir plusieurs onglets et naviguer dans différentes pages. Cette approche améliore l'authenticité de votre scénario de test de charge, ressemblant étroitement aux interactions des utilisateurs avec votre application.
# Attempt to connect to the browser using the provided profile URL try: browser = await connect({'browserWSEndpoint': browser_url, 'defaultViewport': None}) except Exception as e: # Handle connection errors and print a message print(f'Error occurred when connecting to the browser: {str(e)}') return # Create a new page in the connected browser page = await browser.newPage() # Introduce a brief delay to ensure the page is ready await asyncio.sleep(2) # Set the viewport dimensions for the page width, height = 1920, 1080 await page.setViewport({'width': width, 'height': height}) # Try to navigate to a specific URL try: await page.goto('https://{your_website}') # Wait for 10 seconds to simulate user interaction await page.waitFor(10000) # Introduce another delay for additional stability await asyncio.sleep(5) except pyppeteer.errors.PageError as e: # Handle page navigation errors and print a message print(f'Error occurred during page navigation: {str(e)}') # Attempt to take a screenshot of the page try: await page.screenshot(path='screen.png', fullPage=True) # Print a success message if the screenshot is captured successfully print('Screenshot taken successfully.') except Exception as e: # Handle screenshot capture errors and print a message print(f'Error occurred during taking a screenshot: {str(e)}')
Les capacités asynchrones de Python, associées aux bibliothèques HTTP, en font un outil polyvalent pour tester la charge des systèmes basés sur le cloud. Cet exemple sert de point de départ aux ingénieurs QA cherchant à découvrir la puissance de Python dans leurs tentatives de tests de charge.
NOTE
Dans mon scénario, le script décrit s'est avéré robuste et percutant. Il s’est avéré un outil utile pour identifier et résoudre de nombreux problèmes. La nature agressive du script a permis d'identifier les problèmes critiques, de faciliter un débogage efficace et d'ouvrir la voie à une expérience utilisateur transparente et améliorée, ce qui est plutôt bon pour un contrôle qualité.
Dans la suite, je discuterai brièvement d'un autre script utilisant le module multitraitement de Python. Cette approche vise à améliorer la génération de charge en exécutant simultanément plusieurs instances du script de test. L'objectif principal du multitraitement est de paralléliser l'exécution d'un script, permettant des interactions simultanées avec le service. Cette approche contraste avec l'approche asynchrone évoquée précédemment, dans laquelle les tâches sont exécutées séquentiellement mais gérées simultanément. Cela ressemble plus à du spam/ddos avec les mêmes requêtes, mais cela peut aussi être très utile.
def get_profiles(): response = requests.get(url=f"{api}", params=PARAMS, headers=headers) return response
def start_profiles(list_of_profiles_uuids): for uuid in list_of_profiles_uuids: # ... (API request to start profile) def stop_profiles(internal_uuids): for uuid in internal_uuids: # ... (API request to stop profile) def run_script(): start_profiles(get_profile_ids()) stop_profiles(list_of_ids)
if __name__ == "__main__": for runs in range(0, 5): processes = [] for i in range(20): p = multiprocessing.Process(target=run_script) processes.append(p) p.start() for p in processes: p.join()
Le multitraitement fournit une stratégie pour les applications de test de charge. Il permet aux ingénieurs QA d’expérimenter différentes méthodologies en fonction des caractéristiques uniques de leurs applications. Alors que les tests asynchrones offrent une gestion efficace des tâches simultanées, le multitraitement excelle dans la parallélisation de l'ensemble du processus de test. Vous pouvez choisir l’approche qui correspond le mieux à leurs objectifs spécifiques en matière de tests de charge et aux exigences de leurs applications.
Un petit rappel :
Cette démo vise à présenter les concepts de base dans un format convivial pour les débutants, en soulignant la simplicité de Python pour les testeurs QA qui s'aventurent dans les tests de performances.
Si vous rencontrez des problèmes de programmation, n'hésitez pas à rechercher des informations sur Google et à demander à vos collègues, à utiliser ChatGPT ou des outils similaires, et à utiliser GitHub Copilot pour une assistance supplémentaire dans l'écriture de vos scripts de test de charge.
Également publié ici .