Seien Sie am 30. April dabei: Vorstellung von Parasoft C/C++test CT für kontinuierliche Tests und Compliance-Exzellenz | Registrierung

Beherrschen Sie das Testen der Java-Anwendungsleistung mit Parasoft

27. Dezember 2023
9 min lesen

Nur eine einzige Codezeile kann Ihre gesamte Softwareleistung beeinträchtigen, wenn sie nicht rechtzeitig erkannt und korrigiert wird. Sehen Sie sich diesen Beitrag an, um zu erfahren, wie Sie Java-Threads überwachen und die spezifischen Codezeilen in Ihrer Anwendung verstehen, die potenzielle Fehler in Ihrer App-Leistung verursachen könnten.

Thread-bezogene Probleme können die Leistung einer Web-API-Anwendung auf eine Weise beeinträchtigen, die oft schwer zu diagnostizieren und schwierig zu lösen ist. Um eine optimale Leistung zu erzielen, ist es wichtig, ein klares Bild vom Verhalten eines Threads zu behalten.

In diesem Beitrag zeige ich Ihnen die Verwendung Parasoft SOAtestLoad Test JVM Threads Monitor, um die Threading-Aktivität einer JVM mit Diagrammen wichtiger Statistiken und konfigurierbaren Thread-Dumps anzuzeigen, die auf die Codezeilen verweisen können, die für Leistungsverluste aufgrund ineffizienter Thread-Verwendung verantwortlich sind. Parasoft SOAtests Leistungstest-Tools ermöglichen es Ihnen, beliebige Funktionstests in Last- und Leistungstests umzuwandeln.

Warum Leistungstests wichtig sind

Serveranwendungen wie Webserver sind für die gleichzeitige Verarbeitung mehrerer Clientanfragen konzipiert. Die Anzahl der Client-Anfragen, die gleichzeitig von einem Server verarbeitet werden, wird üblicherweise als „Auslastung“ bezeichnet. Eine zunehmende Last kann zu langsamen Reaktionszeiten, Anwendungsfehlern und schließlich zu einem Absturz führen. Thread-bezogene Parallelitäts- und Synchronisierungsprobleme, die durch gleichzeitige Anforderungsverarbeitung verursacht werden, können zu all diesen Kategorien unerwünschten Verhaltens beitragen. Aus diesem Grund sollten sie gründlich getestet und aussortiert werden, bevor die Anwendung in der Produktion eingesetzt wird.

Einer der größten Unterschiede, der trennt Leistungstest Von anderen Arten von Softwaretests unterscheidet sich die systematische Fokussierung auf Parallelitätsprobleme. Der Multithread-Mechanismus, den Serveranwendungen nutzen, um mehrere Client-Anfragen gleichzeitig zu verarbeiten, kann dazu führen, dass Anwendungscode durch Parallelität verursachte Fehler und Ineffizienzen aufweist, die bei anderen, typischerweise Single-Threaded-Testtypen, wie Unit- oder Integrationstests, nicht auftreten.

Aus der Perspektive der Validierung von Anwendungscode auf Thread-bezogene Parallelitäts- und Synchronisierungsprobleme hat ein Leistungstest zwei Ziele.

  1. Um diese Probleme aufzudecken, falls vorhanden.
  2. Um relevante Details zu erfassen, wenn diese Probleme auftreten, und maximale Diagnoseinformationen bereitzustellen, um sie zu beheben.

Um das erste dieser Ziele zu erreichen, muss die zu testende Anwendung (AUT) einer Belastung ausgesetzt werden, die mit der Belastung in der Produktion vergleichbar ist, indem ein Strom simulierter Clientanforderungen mit ordnungsgemäß konfigurierter Lastgleichzeitigkeit, Anforderungsintensität und Testdauer angewendet wird. Weitere Informationen finden Sie unter Best Practices-Leitfaden für Leistungstests.

Das Erreichen des zweiten dieser Ziele ist das Thema dieses Blogbeitrags.

Best Practices für Leistungstests von Java-Anwendungen

Wenn es darum geht, Thread-bezogene Probleme und deren Details zu erkennen, sind die Hauptfragen, wie sie erkannt werden, was mit den Diagnosedaten zu tun ist und wann nach solchen Problemen gesucht werden muss. Nachfolgend finden Sie eine Liste mit den wichtigsten Antworten auf diese Fragen.

So erkennen Sie Thread-bezogene Probleme und nutzen die Diagnosedaten

  • Überwachen Sie AUT-Threads auf Deadlocks sowie BLOCKED- oder PARKED-Zustände, die Threads an der Arbeit hindern.
  • Erstellen Sie Thread-Monitor-basierte Leistungsregressionskontrollen, um solche Probleme automatisch zu erkennen.
  • Erfassen Sie Details wie Stack-Traces von problematischen AUT-Threads, um unerwünschtes Thread-Verhalten zu diagnostizieren, wenn es auftritt.
  • Überlagern Sie Diagramme zu wichtigen Leistungsindikatoren, z. B. Diagramme zur maximalen Antwortzeit und dergleichen, mit Thread-Überwachungsdiagrammen, um Korrelationen zwischen Thread-Problemen und der Anwendungsleistung zu ermitteln.

Fragen Sie den Anbieter Ihres Leistungstestprodukts, wie dessen Tool Ihnen dabei helfen kann, AUT-Threading-Probleme zu erkennen und zu diagnostizieren.

Wann Sie nach themenbezogenen Problemen suchen sollten

Das scheinbar zufällige Muster einiger Thread-bezogener Probleme stellt zusätzliche Probleme dar Herausforderungen bei Leistungstests beim Erkennen und Diagnostizieren seltener Fehler. Die Behebung eines seltenen, eins zu einer Million Fehlers kann in den späten Phasen des Anwendungslebenszyklus zu einem Albtraum für Qualitätssicherungs- und Entwicklungsteams werden.

Um die Wahrscheinlichkeit zu erhöhen, solche Fehler zu erkennen, sollte die Anwendung unter Last während aller Phasen des Leistungstests kontinuierlich überwacht werden. Nachfolgend finden Sie eine Liste der wichtigsten Leistungstesttypen und warum Sie bei der Ausführung Thread-Monitore verwenden sollten.

  • Rauch- oder Basistests um Thread-bezogene Probleme frühzeitig zu erkennen.
  • Belastungstests Stellen Sie sicher, dass die AUT unter der erwarteten Produktionslast nicht unter diesen Problemen leidet.
  • Stresstests um den Grad der Parallelität zu überprüfen. Sie kann bei einem Stresstest viel höher sein als bei einem regulären Lasttest, was die Wahrscheinlichkeit des Auftretens von Thread-bezogenen Problemen erhöht.
  • Ausdauertests kann viel länger laufen als normale Auslastungstests, was die Wahrscheinlichkeit des Auftretens seltener Thread-bezogener Probleme erhöht.

Siehe die Best Practices-Leitfaden für Leistungstests Weitere Informationen zu Leistungstesttypen und deren Verwendung finden Sie hier.

Als Beispiel dafür, wie diese Praktiken angewendet werden können, werden wir nun ein hypothetisches Java-Entwicklungsteam verfolgen, das beim Erstellen einer Web-API-Anwendung auf einige häufige Threading-Probleme stößt, und einige häufige Thread-bezogene Leistungsprobleme diagnostizieren. Danach schauen wir uns komplexere Beispiele der realen Anwendungen an. Beachten Sie, dass in den folgenden Beispielen teilweise suboptimaler Code absichtlich zur Demonstration hinzugefügt wurde.

Die Fallstudie zur Bankanwendung

Unser hypothetisches Java-Entwicklungsteam startete ein neues Projekt: eine REST-API-Banking-Anwendung. Das Team richtete eine Infrastruktur für kontinuierliche Integration (CI) ein, um das neue Projekt zu unterstützen, zu der auch ein regelmäßiger CI-Job mit Parasoft gehört SOAtest's Lastprüfung Modul, um die Leistung der neuen Anwendung kontinuierlich zu testen.

Erhalten Sie Schritt-für-Schritt-Anleitungen zum Einrichten automatisierter Leistungstests.

Bankanwendung Version 1: Race Conditions in der Erstimplementierung

Der Bank-Anwendungscode wächst und die Tests werden ausgeführt. Das Team bemerkte dies jedoch nach der Implementierung eines neuen transfer Betrieb, begann die Bank-Anwendung sporadische Fehler unter höherer Last. Der Fehler ist auf die Kontovalidierungsmethode zurückzuführen, bei der gelegentlich ein negativer Saldo in überziehungsgeschützten Konten festgestellt wird. Der Fehler bei der Kontoüberprüfung führt zu einer Ausnahme und einer HTTP 500-Antwort von der API. Die Entwickler vermuten, dass dies durch eine Rennbedingung in der verursacht werden kann IAccount.withdraw Methode, wenn sie von verschiedenen Threads gleichzeitig aufgerufen wird transfer Betrieb auf dem gleichen Konto:

13: public boolean transfer(IAccount from, IAccount to, int amount) {
14:    if (from.withdraw(amount)) {
15:       to.deposit(amount); 
16:       return true; 
17:    } 
18:    return false; 
19: }

Bankanwendung Version 2: Synchronisierung hinzufügen

Die Entwickler beschließen, den Zugriff auf Konten innerhalb des zu synchronisieren transfer Betrieb zur Verhinderung des vermuteten Rennzustands:

14: public boolean transfer(IAccount from, IAccount to, int amount) {
15:    synchronized (to) {
16:       synchronized (from) {
17:          if (from.withdraw(amount)) {
18:             to.deposit(amount);
19:             return true; 
20:          } 
21:       } 
22:    } 
23:    return false; 
24: }

Das Team fügt außerdem den JVM Threads Monitor zum Lasttestprojekt hinzu, das für die REST-API-Anwendung ausgeführt wird. Der Monitor stellt Diagramme von blockierten, blockierten, geparkten und Gesamtthreads bereit und zeichnet Dumps von Threads in diesen Zuständen auf.

Die Codeänderung wird in das Repository übertragen und vom CI-Leistungstestprozess erfasst. Am nächsten Tag stellen die Entwickler fest, dass der Leistungstest über Nacht fehlgeschlagen ist. Die Bankanwendung reagierte kurz nach Beginn des Leistungstests für den Überweisungsvorgang nicht mehr. Eine Untersuchung der JVM-Threads-Monitor-Diagramme im Lasttestbericht zeigt schnell, dass es in der Bankanwendung blockierte Threads gibt. Siehe Abb. 1.a. Die Deadlock-Details wurden vom JVM Threads Monitor als Teil des Berichts gespeichert und zeigen die genauen Codezeilen, die für den Deadlock verantwortlich sind. Siehe Listing 1.b.

Diagramm, das die Anzahl blockierter Threads in der getesteten Anwendung (AUT) zeigt.
Abb. 1.a: Anzahl der blockierten Threads in der getesteten Anwendung (AUT).
DEADLOCKED thread: http-nio-8080-exec-20     
com.parasoft.demo.bank.v2.ATM_2.transfer:15     
com.parasoft.demo.bank.ATM.transfer:21    
...    
Blocked by:        
DEADLOCKED thread: http-nio-8080-exec-7
com.parasoft.demo.bank.v2.ATM_2.transfer:16
com.parasoft.demo.bank.ATM.transfer:21            
com.parasoft.demo.bank.v2.RestController_2.transfer:29
sun.reflect.GeneratedMethodAccessor58.invoke:-1
sun.reflect.DelegatingMethodAccessorImpl.invoke:-1   
java.lang.reflect.Method.invoke:-1            
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke:209

Listing 1.b: Vom JVM-Threads-Monitor gespeicherte Deadlock-Details.

Bankanwendung Version 3: Deadlocks beheben

Die Entwickler der Bankanwendung beschließen, den Deadlock zu beheben, indem sie ein einzelnes globales Objekt synchronisieren und den Code der Übertragungsmethode wie folgt ändern:

14: public boolean transfer(IAccount from, IAccount to, int amount) {
15:     synchronized (Account.class) {
16:       if (from.withdraw(amount)) {
17:          to.deposit(amount); 
18:             return true; 
19:       } 
20:    }
21:    return false; 
22: }

Die Änderung behebt das Deadlock-Problem von Version 2 und die Race-Bedingung von Version 1, aber den Durchschnitt transfer Die Reaktionszeit des Betriebs erhöht sich um mehr als das Fünffache von 30 auf über 150 Millisekunden. Siehe Abb. 2.a. Das BlockedRatio-Diagramm von JVM Threads Monitor zeigt, dass sich 60 bis 75 Prozent der Anwendungsthreads während der Ausführung des Lasttests im Status BLOCKED befinden. Siehe Abb. 2.b. Die vom Monitor gespeicherten Details weisen darauf hin, dass Anwendungsthreads blockiert sind, während sie versuchen, in den global synchronisierten Abschnitt in Zeile 15 einzutreten. Siehe Listing 2.c.

Zwei Diagramme nebeneinander. Abb. 2a zeigt die Antwortzeiten der Bankanwendung Version 3 mit einer blauen Linie, die auf 160 Prozent springt und stabil bleibt, im Vergleich zu den Antwortzeiten der Version 1 mit einer grünen Linie, die 40 Prozent erreicht, mit leichten Abwärtsschwankungen. Auf der rechten Seite ist Abb. 2.b zu sehen, die den Prozentsatz der blockierten Threads im AUT zeigt, der 70 erreicht und dann nach 50 Sekunden Abschlusszeit auf 100 abfällt.

BLOCKED thread: http-nio-8080-exec-4    
com.parasoft.demo.bank.v3.ATM_3.transfer:15    
com.parasoft.demo.bank.ATM.transfer:21    
com.parasoft.demo.bank.v3.RestController_3.transfer:29    
...    
Blocked by:        
SLEEPING thread: http-nio-8080-exec-8            
java.lang.Thread.sleep:-2            
com.parasoft.demo.bank.Account.doWithdraw:64           
com.parasoft.demo.bank.Account.withdraw:31

Listing 2.c: Details zu blockierten Threads, die vom JVM Threads Monitor gespeichert wurden.

Bankanwendung Version 4: Verbesserung der Synchronisierungsleistung

Das Entwicklerteam sucht nach einem Fix, der den Race-Zustand löst, ohne Deadlocks einzuführen und die Reaktionsfähigkeit der Anwendung zu beeinträchtigen, und findet nach einigen Recherchen eine vielversprechende Lösung unter Verwendung von java.util.concurrent.locks.ReentrantLock Klasse:

19: private boolean doTransfer(Account from, Account to, int amount) {           
20:    try
21:        acquireLocks(from.getReentrantLock(), to.getReentrantLock()); 
22:        if (from.withdraw(amount)) {
23:            to.deposit(amount); 
24:            return true; 
25:        } 
26:        return false; 
27:    } finally {
28:         releaseLocks(from.getReentrantLock(), to.getReentrantLock()); 
29:    } 
30: } 

Die Grafiken in Abb. 3a zeigen die Antwortzeiten der Bankanwendung transfer Betrieb von Version 4 (optimiertes Sperren) im roten Diagramm, Version 3 (globale Objektsynchronisierung) im blauen Diagramm und Version 1 (unsynchronisierter Übertragungsvorgang) im grünen Diagramm. Die Grafiken zeigen, dass die transfer Die Betriebsleistung hat sich durch die Verriegelungsoptimierung dramatisch verbessert. Der geringfügige Unterschied zwischen dem synchronisierten (rotes Diagramm) und dem nicht synchronisierten (grünes Diagramm) transfer Der Betrieb ist ein akzeptabler Preis, um die Rennbedingungen zu verhindern.

Diagramm, das die Reaktionszeit des Übertragungsvorgangs der Bankanwendung Version 4 (rot), Version 3 (blau) und Version 1 (grün) zeigt.
Abb. 3.a: Reaktionszeit des Übertragungsvorgangs der Bankanwendung Version 4 (rot), Version 3 (blau) und Version 1 (grün).

Leistungsherausforderungen aus der Praxis

Beispiel 1: Wachsende Reaktionszeit der Anwendung

Die obigen Bankanwendungsbeispiele zeigen, wie typische Einzelfälle von Leistungseinbußen aufgrund von Threading-Problemen behoben werden können. Die realen Fälle könnten komplizierter sein. Die Diagramme in Abb. 4 zeigen ein Beispiel einer Produktions-REST-API-Anwendung, deren Antwortzeit im Verlauf des Leistungstests immer weiter zunahm. Die Reaktionszeit der Anwendung wuchs in der ersten Hälfte des Tests langsamer und in der zweiten Hälfte stärker. Siehe Abb. 4.a.

In der ersten Hälfte des Tests korrelierte der Anstieg der Antwortzeit mit der Gesamtzeit, die Anwendungsthreads im BLOCKIERTEN Zustand verbrachten. Siehe Abb. 4.b.

In der zweiten Hälfte des Tests korrelierte der Anstieg der Antwortzeit mit der Anzahl der Anwendungsthreads im PARKED-Zustand. Siehe Abb. 4.c.

Die vom Load Test JVM Threads Monitor erfassten Stack-Traces lieferten die Details: Einer zeigte auf a synchronized Block, der für übermäßige Zeit im BLOCKED-Zustand verantwortlich war. Der andere zeigte auf die verwendeten Codezeilen java.util.concurrent.locks Klassen für die Synchronisation, die dafür verantwortlich waren, dass Threads im Status PARKED gehalten wurden. Nachdem diese Codebereiche optimiert wurden, wurden beide Leistungsprobleme behoben.

Drei Grafiken zeigen. Abb. 4.a. Zeigt den Anstieg der Reaktionszeit der Anwendung auf 10,000 ab 1500 Testabschlusssekunden. Abb. 4.b. Zeigt die Zeit an, die Anwendungsthreads in einem blockierten Zustand verbracht haben. Abb. 4.c zeigt die Anzahl der Anwendungsthreads im geparkten Zustand.

Beispiel 2: Gelegentliche Spitzen in der Anwendungsantwortzeit

Der Load Test JVM Threads Monitor kann bei der Erfassung von Details seltener Thread-bezogener Probleme sehr hilfreich sein, insbesondere wenn Ihre Leistungstests automatisiert sind und regelmäßig ausgeführt werden*. Die Diagramme in Abb. 5 zeigen eine Produktions-REST-API-Anwendung, die zeitweise Spitzen bei den durchschnittlichen und maximalen Antwortzeiten aufwies. Siehe Abb. 5.a.

Solche Spitzen in der Anwendungsantwortzeit können oft durch eine suboptimale JVM-Garbage-Collector-Konfiguration verursacht werden, aber in diesem Fall deutet ein korrelierender Spitzenwert im BlockedTime-Monitor, Abb. 5.b, auf die Thread-Synchronisierung als Ursache des Problems hin. Der BlockedThreads-Monitor hilft hier noch mehr, indem er die Stack-Traces der blockierten und blockierenden Threads erfasst. Es ist wichtig, den Unterschied zwischen den BlockedTime- und den BlockedThreads-Monitoren zu verstehen.

Der BlockedTime-Monitor zeigt die kumulierte Zeit an, die JVM-Threads zwischen Monitoraufrufen im BLOCKED-Status verbracht haben, während der BlockedThreads-Monitor regelmäßig Snapshots von JVM-Threads erstellt und in diesen Snapshots nach blockierten Threads sucht. Aus diesem Grund ist der BlockedTime-Monitor bei der Erkennung von Thread-Blockaden zuverlässiger, weist Sie jedoch lediglich darauf hin, dass Probleme mit der Thread-Blockierung vorliegen.

Der BlockedThreads-Monitor übersieht möglicherweise einige Thread-blockierende Ereignisse, da er regelmäßig Thread-Snapshots erstellt. Positiv ist jedoch, dass er bei der Erfassung solcher Ereignisse detaillierte Informationen darüber liefert, was die Blockierung verursacht. Aus diesem Grund ist es eine Frage der Statistik, ob ein BlockedThreads-Monitor die codebezogenen Details eines blockierten Zustands erfasst oder nicht. Wenn Ihre Leistungstests jedoch regelmäßig ausgeführt werden, werden Sie bald einen Spitzenwert im BlockedThreads-Diagramm feststellen (siehe Abb. 5.c), was bedeutet, dass blockierte und blockierende Thread-Details erfasst wurden. Diese Details weisen Sie auf die Codezeilen hin, die für die seltenen Spitzen in der Antwortzeit der Anwendung verantwortlich sind.

Drei Diagramme nebeneinander dargestellt. Abb. 5.a. zeigt einen Anstieg der maximalen (gelb) und durchschnittlichen (grau) Reaktionszeiten der Anwendung. Abb. 5.b. zeigt eine entsprechende Spitze im BlockedTime-Monitor. Abb. 5.c. zeigt einen entsprechenden Anstieg im Blocked Threads-Monitor an.

Erstellen von Leistungsregressionssteuerungen

Der Load Test JVM Threads-Monitor ist nicht nur ein effektives Diagnosetool, sondern kann auch zum Erstellen von Leistungsregressionskontrollen für Thread-bezogene Probleme verwendet werden. Nachdem Sie ein solches Leistungsproblem entdeckt und behoben haben, erstellen Sie einen Leistungsregressionstest dafür. Der Test besteht aus einem bestehenden oder einem neuen Leistungstestlauf und einer neuen Regressionskontrolle. Im Fall von Parasoft Load Test wäre dies eine QoS-Monitor-Metrik für einen relevanten JVM-Threads-Monitor-Kanal. Erstellen Sie beispielsweise für das in Beispiel 1, Abb. 4 beschriebene Problem eine Lasttest-QoS-Monitor-Metrik, die die Zeit überprüft, die Anwendungsthreads im GESPERRT-Zustand verbracht haben, und eine weitere Metrik, die die Anzahl der Threads im GEPARK-Zustand überprüft. Es ist immer eine gute Idee, benannte Threads in Ihrer Java-Anwendung zu erstellen. Dadurch können Sie Leistungsregressionskontrollen auf eine nach Namen gefilterte Gruppe von Threads anwenden.

Die Rolle der Automatisierung beim Leistungstest von Java-Anwendungen

Sobald die JVM-Threads-Monitor-Regressionskontrollen erstellt wurden, können sie effektiv in automatisierten Leistungstests eingesetzt werden. Leistungsregressionskontrollen sind ein unverzichtbares Werkzeug für die Testautomatisierung, die wiederum ein wichtiges Element der kontinuierlichen Testautomatisierung darstellt Leistungstests innerhalb einer DevOps-Pipeline. Leistungsrückgangskontrollen sollten nicht nur als Reaktion auf frühere Leistungsrückgänge, sondern auch als Schutz vor potenziellen Problemen erstellt werden.

Die folgende Tabelle enthält eine Zusammenfassung der zu verwendenden Threads Monitor-Kanäle und wann:

Threads-Monitor-KanalWann zu verwenden
Deadlock-ThreadsImmer. Deadlocks sind wohl die schwerwiegendsten Thread-Probleme, die die Anwendungsfunktionalität vollständig beeinträchtigen könnten.
ÜberwachenDeadlockedThreads
Blockierte ThreadsImmer. Übermäßige Zeit im BLOCKED-Status oder die Anzahl der BLOCKED-Threads führt normalerweise zu Leistungseinbußen. Überwachen Sie mindestens einen dieser Parameter. Wird auch für Steuerelemente zur Leistungsregression verwendet.
Blockierte Zeit
Sperrverhältnis
Anzahl blockiert
GeparkteThreadsImmer. Eine übermäßige Anzahl von Threads im Status PARKED kann auf eine missbräuchliche Verwendung der Klassen java.util.concurrent.locks und andere Threading-Probleme hinweisen. Wird auch für Steuerelemente zur Leistungsregression verwendet.
GesamtThreadsHäufig. Verwenden Sie diese Option, um die Anzahl der Threads in BLOCKED, PARKED oder anderen Zuständen mit der Gesamtzahl der Threads zu vergleichen. Wird auch für Steuerelemente zur Leistungsregression verwendet.
SchlafenThreadsGelegentlich. Verwendung für Leistungsregressionskontrollen in Bezug auf diese Zustände und für Erkundungstests.
WartenThreads
Wartezeit
Wartequote
WaitingCount
NeueThreadsSelten. Verwendung für Steuerelemente zur Leistungsregression in Bezug auf diese Thread-Zustände.
Unbekannte Themen

Zusammenfassung

Der JVM-Thread-Monitor von Parasoft ist ein effektives Diagnosetool zum Erkennen von Thread-bezogenen JVM-Leistungsproblemen sowie zum Erstellen erweiterter Steuerelemente für die Leistungsregression. In Kombination mit SOAtest's Load Test Continuum hilft der JVM Threads Monitor, den Schritt zur Reproduktion von Leistungsproblemen zu eliminieren, indem relevante Thread-Details aufgezeichnet werden, die auf die Codezeilen hinweisen, die für schlechte Leistung verantwortlich sind, und Ihnen helfen, sowohl die Anwendungsleistung als auch die Entwickler- und QA-Produktivität zu verbessern.

Best Practices-Leitfaden für Leistungstests