Blog.

Leistungstests mit Selenium WebDriver: Hohe Antwortzeiten erkennen
Das Problem
Hier bei FloQast haben wir die kürzlich veröffentlicht Alle Arbeitsabläufe Dashboard die es den Nutzern ermöglicht, sich einen Überblick über den Fortschritt der einzelnen Arbeitsabläufe zu verschaffen. Diese neue Seite enthält einen Filter, mit dem Sie eine Zeitleiste mit 2 Typen sehen können: Arbeitsablauf und Teammitglied.
Das Teammitglied und die Workflow-Filter haben mehrere Endpunkte, die Daten zur Anzeige in ihren Zeitleisten anfordern. Leider dauerten die Ladezeiten zwischen den einzelnen Filtertypen 1-2 Minuten für eine Antwort.
Die Entwickler haben das Problem schnell erkannt und an einer Lösung gearbeitet, aber die Frage, die sich uns stellte, war: "Wie können wir dieses Problem in Zukunft früher erkennen?" In diesem Moment wurde uns klar, dass wir unser QA Automation Framework um einen Leistungstest erweitern mussten.
Nachdem wir uns darauf geeinigt hatten, war die nächste Frage: "Wo sollen die Ergebnisse der einzelnen Leistungstests gespeichert werden?" Ich schlug vor, die Google Sheets API zu verwenden, um die Antwortzeiten der einzelnen Tests in einem gemeinsamen Google Sheet zu speichern und den Leistungstest jeden Morgen automatisch mit Jenkins durchzuführen.
Google Sheets API
Zuerst habe ich eine Methode für die Google-API-Authentifizierung erstellt, die wiederverwendet werden kann:
Hinweis: siehe Google Sheets API Blog
npm install googleapis oder yarn add googleapis
import { google } from 'googleapis'; import { logger } from '../../src/lib/logger'; import { GOOGLE_SHEETS_KEYS } from '../constants'; const creds = { type: GOOGLE_SHEETS_KEYS.TYPE, project_id: GOOGLE_SHEETS_KEYS.PROJECT_ID, private_key_id: GOOGLE_SHEETS_KEYS.PRIVATE_KEY_ID, private_key: GOOGLE_SHEETS_KEYS.PRIVATE_KEY, client_email: GOOGLE_SHEETS_KEYS.CLIENT_EMAIL, client_id: GOOGLE_SHEETS_KEYS.CLIENT_ID, auth_uri: GOOGLE_SHEETS_KEYS.AUTH_URI, token_uri: GOOGLE_SHEETS_KEYS.TOKEN_URI, auth_provider_x509_cert_url: GOOGLE_SHEETS_KEYS.AUTH_PROVIDER_X509_CERT_URL, client_x509_cert_url: GOOGLE_SHEETS_KEYS.CLIENT_X509_CERT_URL }; export const GoogleAPI = { spreadsheetId: GOOGLE_SHEETS_KEYS.SPREAD_SHEET_ID, authentication: async function () { logger.info('Generating Google Api Authentication'); const auth = new google.auth.GoogleAuth({ credentials: creds, scopes: 'https://www.googleapis.com/auth/spreadsheets' }); const authClientObject = await auth.getClient(); return { authClientObject, auth }; } };
Dieser Code:
- importiert das googleapis-Paket.
- importiert ein personalisiertes Logger-Modul zur Ausgabe auf der Konsole.
- importiert ein Konstantenmodul mit den erforderlichen Anmeldedaten für die Google-API-Authentifizierung.
- exportiert das GoogleAPI-Objekt zur späteren Verwendung mit dem Schlüssel
spreadsheetId
die Tabellenkalkulation, in der die Daten gespeichert werden, und einauthentication
Methode.
Nachdem wir das obige Stück Code haben, können wir nun mit etwas wie diesem aufwarten:
import { google } from 'googleapis'; import { logger } from '../../src/lib/logger'; import { GoogleAPI } from '../../src/api/GoogleAPI'; const spreadsheetId = GoogleAPI.spreadsheetId; /** * Data to send to google sheets * @param {array} data - multidimensional array with [data,data,data] */ export const insertPerformanceTestData = async (data) => { const { auth, authClientObject } = await GoogleAPI.authentication(); const googleSheetsInstance = google.sheets({ version: 'v4', auth: authClientObject }); logger.info('Adding performance test result data to sheet: Performance Test '); await googleSheetsInstance.spreadsheets.values.append({ auth, spreadsheetId, range: 'Performance Test!A:K', // columns A to K where data is going to be inserted. valueInputOption: 'USER_ENTERED', resource: { values: data } }); };
Dieser Code:
- importiert das googleapis-Paket.
- importiert ein personalisiertes Logger-Modul zur Ausgabe auf der Konsole.
- importiert das GoogleAPI-Objekt mit der Authentifizierungsmethode.
- exportiert eine Funktion, die die Blattmethode verwendet, um Leistungsdaten in die Spalten von A bis K einzufügen.
Die Testhelfer
Zuerst habe ich ein paar Hilfsfunktionen erstellt, die ich später verwenden kann:
import { std, mean } from 'mathjs'; import { logger } from '../../src/lib/logger'; import moment from 'moment'; export let requestIterations = process.env.REQUEST_ITERATIONS || 20; export let pandoraPerformanceDataSize = process.env.PERFORMANCE_DATA_SIZE || 'average'; pandoraPerformanceDataSize = pandoraPerformanceDataSize.charAt(0).toUpperCase() + pandoraPerformanceDataSize.slice(1); function msToTime(ms) { let seconds = (ms / 1000).toFixed(1); if (seconds < 60) return `${seconds} Sec`; let minutes = (ms / (1000 * 60)).toFixed(1); if (minutes < 60) return `${minutes} Min`; let hours = (ms / (1000 * 60 * 60)).toFixed(1); if (hours < 24) return `${hours} Hrs`; let days = (ms / (1000 * 60 * 60 * 24)).toFixed(1); return `${days} Days`; } export const performanceResults = (requestTimes) => { if (!requestTimes || requestTimes.length === 0) return 0; // to avoid divide by 0 return { stdDeviation: msToTime(std(requestTimes)), mean: msToTime(mean(requestTimes)), max: msToTime(Math.max(...requestTimes)) }; }; export const performRequestIterations = async ({ times = requestIterations, callback, inParallel = true }) => { if (inParallel) { let requestTimes = []; for (let index = 1; index <= times; index++) { let time = getResponseTime(callback); requestTimes.push(time); } return Promise.all(requestTimes); } let requestTimes = []; for (let i = 1; i <= times; i++) { let time = await getResponseTime(callback); requestTimes.push(time); logger.info(`Iteration #${i} out of ${times}`); } return requestTimes; }; export const getResponseTime = async (callback) => { const startTime = Date.now(); await callback(); return Date.now() - startTime; }; const performanceDataWrapper = (requestTimes) => { const performance = performanceResults(requestTimes); const requesTimesToTime = requestTimes.map((t) => msToTime(t)); return { mean: performance.mean, max: performance.max, stdDeviation: performance.stdDeviation, date: moment().format('MMMM Do YYYY, h:mm:ss a'), times: requesTimesToTime }; }; export const assignResults = ({ requestTimes, query, results }) => { const { date, mean, max, stdDeviation, times } = performanceDataWrapper(requestTimes); const _results = [ query.component, //A query.periods, //B query.workflow, //C pandoraPerformanceDataSize, //D date, //E query.periods, //F requestIterations.toString(), //G times.toString().split(',').join(', '), //H mean,//I max,//J stdDeviation//K ]; results.push(_results); };
Da die Antwortzeiten in Millisekunden angegeben werden, habe ich die Funktion "msToTime" hinzugefügt, die Millisekunden in Sekunden, Minuten, Stunden oder Tage umrechnet, und zwar auf der Grundlage der in der Funktion angegebenen Anzahl von Millisekunden.
Bei der Leistung ist der Test für die Berechnung der Standardabweichung von entscheidender Bedeutung, da sie die Stabilität der Anwendung, in diesem Fall eines Endpunkts, darstellt. Deshalb wurde die Funktion "performanceResults" erstellt, um die Standardabweichung, den Mittelwert (oder Durchschnitt) und den Höchstwert der Antwortzeiten der Anfragen mithilfe des math.js npm-Pakets einfach zu berechnen.
Die Funktionen:
getResponseTime
ist eine Funktion, die einen Callback empfängt, d.h. die HTTP-Anforderungsfunktion, die dafür zuständig ist, eine Anforderung an einen Endpunkt zu senden und Daten zurückzuerhalten. In diesem Fall wollen wir nur die Antwortzeit wissen, wir wollen nicht die Daten selbst überprüfen.requestIterations
Konstante ist eine Variable, die von Jenkins einen Wert erhält, der die Anzahl der auszuführenden Anfragen angibt.pandoraPerformanceDataSize
ist eine konstante Variable, die von Jenkins einen Wert erhält, der die Datenmenge angibt, die für die Durchführung eines Leistungstests verwendet werden soll.performRequestIterations
ist eine Funktion, die dafür sorgt, dass die gewünschten Mengenanforderungen parallel oder nacheinander ausgeführt werden.performanceDataWrapper
ist eine Funktion, die die Ergebnisse kapselt, um sie später leicht in derassignResults
Funktion einer beliebigen anderen Funktion, die wir hinzugefügt haben.assignResults
eine Funktion, die die Ergebnisse formatiert und das Ergebnis in ein Array "passed by reference" verschiebt.
Schreiben des Tests
Jetzt können wir den Test schreiben, der die performRequestIterations
, insertPerformanceTestData
und assignResults
Methoden, die als Hilfsfunktionen erstellt wurden.
describe('All workflow dashboard performance test Team Member Overview', () => { let headers, workflows; let results = []; let data = { periods: 'July_2021,June_2021,August_2021', pathType: 'overview', }; beforeAll(async () => { headers = await generatePandoraPerformanceHeaders(); //authentication based on selected pandoraPerformanceDataSize workflows = await UsersAPI.getUserWorkflows(headers); }); afterAll(async () => { await insertPerformanceTestData(results); // here we insert the data to Google Sheets after test is done. }); it('team member timeline data panel cards', async () => { const _workflows = workflows.body .filter((workflow) => workflow.companyIds.length > 0) .map((workflows) => workflows.type) .toString(); const query = { ...data, workflow: _workflows, headers, component: 'Team Member Panel Card', verbose: false, }; const requestTimes = await performRequestIterations({ callback: () => BiAPI.getTeamMemberTimelineOrOverview(query), //method that makes the request. }); assignResults({ requestTimes, query, results }); }); });
Die Ergebnisse
Nachdem wir Jenkins mit der obigen Testdatei eingerichtet und mit 20 Iterationen ausgeführt haben, erhalten wir dieses Ergebnis:
Gelernte Lektionen
- Leistungstests sind wichtig, weil sie die Stabilität der Anwendung darstellen.
- Berechnen Sie die Standardabweichung, den Mittelwert (oder Durchschnitt) und den Höchstwert der Antwortzeiten.
- Automatisieren Sie den Leistungstest so, dass er automatisch zu einem bestimmten Zeitpunkt durchgeführt werden kann.
- Speichern Sie die Ergebnisse der Leistungstests für eine spätere Analyse.
Wenn Sie mehr über Leistungstests erfahren möchten, lesen Sie hier:
Zurück zu Blog