Erfahren Sie, wie die Continuous Quality Platform von Parasoft dabei hilft, Testumgebungen zu steuern und zu verwalten, um zuverlässig hochwertige Software zu liefern. Für Demo registrieren >>

BLOG

Erstellen der API-Leistung von Grund auf: Verwenden von Komponententests zum Benchmarking der Leistung von API-Komponenten

Erstellen der API-Leistung von Grund auf: Verwenden von Komponententests zum Benchmarking der Leistung von API-Komponenten Lesezeit: 7 Minuten
Leistungstests auf Einheitenebene sind eine leistungsstarke Methode, die Sie mit einer engen Integration zwischen Ihren Unit-Test- und Leistungstest-Tools problemlos erreichen können, damit Sie die Leistung der Komponenten verstehen, die Sie in Ihre Zielanwendung integrieren.

APIs wurden ursprünglich als grundlegende Integrationswerkzeuge konzipiert, die es unterschiedlichen Anwendungen ermöglichten, Daten auszutauschen. Sie haben sich jedoch zu einem kritischen Klebstoff entwickelt, der mehrere Prozesse in einer einzigen Anwendung verbindet. Moderne Anwendungen aggregieren und verbrauchen APIs in erstaunlichem Tempo, um Geschäftsziele zu erreichen. Tatsächlich basiert die Geschäftslogik der meisten modernen Anwendungen jetzt auf einer Kombination von APIs und Bibliotheken von Drittanbietern. Dies bedeutet, dass die Leistung von End-to-End-Transaktionen stark von der Leistung der APIs und Komponenten abhängt, die die Anwendung nutzt .

Aufgrund ihrer Fähigkeit, Leistungsziele zu erreichen oder zu brechen, müssen wichtige Anwendungskomponenten als integraler Bestandteil des Akzeptanzprozesses einer strengen Leistungsbewertung unterzogen werden. Je früher Sie die Leistungsfähigkeiten der Schlüsselkomponenten einer Anwendung verstehen, desto einfacher ist es, Probleme zu beheben, und desto effektiver können Sie sicherstellen, dass die integrierte Anwendung ihre Leistungsanforderungen erfüllt.

Unit-Tests

Die Verwendung von Komponententests zur Bewertung der Komponentenleistung bietet eine Reihe von Vorteilen, darunter die folgenden: 

  • Unit-Tests bieten eine flexible, aber standardisierte Testmethode auf Komponentenebene.
  • Unit-Tests sind in Entwicklungsumgebungen gut bekannt und weit verbreitet.
  • In der Regel erfordern Komponententests nur einen Bruchteil der Hardwareressourcen, die zum Testen der gesamten Anwendung erforderlich sind. Dies bedeutet, dass Sie die Komponenten frühzeitig und häufiger mit den in Entwicklungsumgebungen verfügbaren Hardwareressourcen auf einem maximal projizierten „Stress“ -Niveau (siehe Abbildung unten) testen können.

Abb. 1: Ungefähre Anteile des Lastniveaus und der Testdauer typischer Leistungstestszenarien

Trotz dieser Vorteile werden Leistungstests auf Einheitenebene häufig übersehen, da den meisten Unit-Test-Tools die Funktionen fehlen, die üblicherweise in dedizierten Leistungstest-Tools zu finden sind (z. B. die Möglichkeit, verschiedene Leistungstestkonfigurationen einzurichten und auszuführen, die Überwachung von System- und Anwendungsressourcen während des Testen, Sammeln und Analysieren von Leistungstestergebnissen usw.).

Sie können jedoch das Beste aus beiden Welten herausholen, indem Sie Tests auf Einheitenebene mit herkömmlichen Leistungstest-Tools ausführen. Im Folgenden werde ich Ihnen auch eine Strategie geben, mit der Sie die Leistung der Komponenten messen und bewerten können, die Ihr Team möglicherweise in Ihre Zielanwendung integriert.

Einrichtung eines Komponenten-Benchmarking-Workflows

Die Notwendigkeit eines Benchmarking auf Komponentenebene in Entwicklungsumgebungen kann in verschiedenen Phasen des Software-Lebenszyklus auftreten und wird von den folgenden Fragen bestimmt:

  • Welche der verfügbaren Komponenten von Drittanbietern erfüllt nicht nur die funktionalen Anforderungen, sondern weist auch die beste Leistung auf? Soll ich Komponente A, B, C usw. verwenden oder eine neue implementieren? Solche Fragen treten normalerweise in der Entwurfs- und Prototypenphase auf.
  • Welche der alternativen Code-Implementierungen ist aus Sicht der Leistung am optimalsten? Solche Fragen treten normalerweise während der Entwicklungsphase auf und beziehen sich auf intern entwickelten Code.

Ein ordnungsgemäß konfigurierter und ausgeführter Komponenten-Benchmark kann bei der Beantwortung dieser Fragen hilfreich sein. Ein typischer Komponenten-Benchmark-Workflow (siehe Abb. 2 unten) besteht aus:

  1. Erstellen von Komponententests für die Benchmark-Komponenten.
  2. Auswahl der Benchmark-Leistungsparameter (diese sind für alle Komponenten gleich).
  3. Ausführen der Leistungstests.
  4. Vergleich der Leistungsprofile verschiedener Komponenten.

Dies ist in Abbildung 2 dargestellt:

Abb. 2: Ein typischer Komponenten-Benchmark-Workflow

Schauen wir uns als konkretes Beispiel an, wie die Leistung von vier JSON-Parser-Komponenten verglichen wird: Jackson (Streaming), JsonSmart, Gson und FlexJson. Hier verwenden wir JUnit als Unit-Test-Framework und Parasoft Load Test als Lasttest-Anwendung, die Teil von ist Parasoft SOAtest. Die gleiche Strategie kann mit anderen Unit-Test-Frameworks und Lasttest-Anwendungen angewendet werden.

Erstellen von Komponententests für die Benchmark-Komponenten

Der JUnit-Test sollte die Komponente wie die Zielanwendung aufrufen. Dies gilt für die Komponentenkonfiguration, die Auswahl der Komponenten-API-Methoden sowie die Werte und Bereiche der Methodenargumente.

Das gewünschte Maß an Ergebnisüberprüfung hängt von der Art der Tests ab. In der Regel würden Sie eine umfassendere Überprüfung der Komponententestergebnisse für Zuverlässigkeitstests durchführen, jedoch eine grundlegendere Überprüfung für Effizienzprüfungen durchführen (da die Überprüfungslogik für "schwere" Ergebnisse das Leistungsbild verzerren kann). Andererseits ist es wichtig, mindestens eine Überprüfung durchzuführen, um sicherzustellen, dass die Komponente ordnungsgemäß konfiguriert und aufgerufen wird.

In unserem Benchmark wird der JSON-Inhalt zu Beginn des Leistungstests aus einer Datei geladen und im Speicher zwischengespeichert. Die JSON-Datei hat eine Größe von 225 KB und enthält 215 Kontobeschreibungsobjekte der obersten Ebene. Jedes Kontoobjekt hat ein Name / Wert-Paar "Kontostand":

{

    "id": "54b98c2b7b3bd64aae699040",

    "Index": 214,

    "guid": "565c44b0-9e6d-4b8e-819c-48aa4dd9d7c2",

    "Saldo": "3,809.46 $",

realisieren kannst...

}

Die grundlegende Ebene der Überprüfung der Komponentenfunktionalität wird folgendermaßen implementiert: Der JUnit-Test ruft die Parser-API auf, um alle "Balance" -Elemente im JSON-Inhalt zu finden und den Gesamtsaldo aller Kontoobjekte im JSON-Dokument zu berechnen. Der berechnete Saldo wird dann mit dem erwarteten Wert verglichen:

Öffentlichkeit Klasse JacksonStreamParserTest erweitert Testfall {

    @Prüfung

    Öffentlichkeit ungültig testParser () wirft IOAusnahme {       

        schweben Saldo = 0;

        JsonFactory jsonFactory = erneuerbare JsonFabrik();

        Zeichenfolge json = JsonContentProvider.getJsonContent();

        JsonParser jp = jsonFactory.createJsonParser(json);           

        während (jp.nextToken ()! = null) {               

            String fieldname = jp.getCurrentName ();

            if (Feldname! = null && Feldname.ist gleich("Balance")) {

                jp.nextToken ();                   

                balance + = TestUtil.parseWährungStr(jp.getText ());  

            }

        }

        TestUtil.Gleichgewicht behaupten assert(Balance);       

    }

}

Da wir Parser auf Effizienz vergleichen, verwenden wir eine einfache Ressourcenüberprüfung, um eine Verzerrung des Leistungsbilds zu vermeiden. Wenn Sie sich für eine Überprüfung der Ergebnisse im Schwergewicht entscheiden, können Sie diese auf dieselbe Weise kompensieren, wie wir den Ressourcenverbrauch des unten beschriebenen Frameworks für Leistungstests kompensieren. Dies erfordert jedoch eine ausführlichere Vorbereitung des Basisszenarios.

Kompensation des Testrahmens

Da wir die Leistung von Komponenten vergleichen, die im selben Prozess wie die Anwendung zum Testen der Containerleistung ausgeführt werden, müssen wir den Anteil der von dieser Anwendung und dem JUnit-Framework verbrauchten Ressourcen auf Systemebene und Anwendungsebene von dem Anteil trennen, der von verwendet wird die Komponente selbst. Um diesen Anteil abzuschätzen, können wir ein Benchmark-Lasttest-Szenario mit einem leeren JUnit-Test ausführen:

Öffentlichkeit Klasse Basistest erweitert Testfall {

    @Prüfung

    Öffentlichkeit ungültig testBaseline () {               

    }

}

Wenn die Ressourcennutzungsgrade des Basisszenarios nicht vernachlässigbar sind, müssen wir diese Levels von den Levels der Komponenten-Benchmark-Läufe subtrahieren, um den Anteil der vom Testframework verbrauchten Ressourcen zu kompensieren.

Auswählen und Konfigurieren von Benchmark-Leistungsparametern

Das Setup der Testumgebung sollte die wichtigsten Parameter der Zielumgebung emulieren, z. B. das Betriebssystem, die JVM-Version, JVM-Optionen wie die GC-Optionen, den Servermodus usw. Es ist möglicherweise nicht immer möglich oder praktisch, alle Bereitstellungsparameter in der Testumgebung zu reproduzieren. Je näher es ist, desto geringer ist jedoch die Wahrscheinlichkeit, dass sich die Komponentenleistung während des Tests von der in der Zielumgebung unterscheidet.

Verständnis von Parallelität, Intensität und Testdauer

Die wichtigsten Leistungstestparameter, die die Bedingungen bestimmen, unter denen die Komponenten getestet werden, sind: Lastniveau (definiert als Belastungsintensität und  Parallelität laden) und Ladedauer (siehe Abb. 3 unten).

Um diese generischen Leistungstestparameter in konkrete Formen zu bringen, untersuchen Sie zunächst die Leistungsanforderungen der Zielanwendung, für die Sie die Verwendung dieser Komponente erwarten. Leistungsanforderungen auf Anwendungsebene können konkrete oder ungefähre Eigenschaften des Lastniveaus liefern, dem das Bauteil ausgesetzt werden soll. Die Umsetzung der Leistungsanforderungen für Anwendungen in Leistungsanforderungen für Komponenten ist jedoch mit mehreren Herausforderungen verbunden. Wenn die Anwendungsleistungsanforderung beispielsweise angibt, dass sie innerhalb von M Millisekunden bei einer Laststufe von N Benutzern oder Anforderungen pro Sekunde antworten muss, wie führt dies zu Leistungsanforderungen für eine Komponente, die Teil dieser Anwendung ist? Wie viele Anforderungen an eine bestimmte Komponente wird eine Anforderung an die Anwendung generieren?

Wenn es eine ältere Version der Anwendung gibt, können Sie eine fundierte Vermutung anstellen, indem Sie einige Anrufe auf Anwendungsebene nachverfolgen oder die von einem APM-Tool (Application Performance Management) gesammelten Anrufverfolgungsstatistiken untersuchen. Wenn keine dieser Optionen verfügbar ist, kann die Antwort aus der Prüfung des Anwendungsdesigns stammen.

Wenn die Komponentenlastparameter nicht aus den Leistungsspezifikationen der Zielanwendung abgeleitet werden können oder wenn eine solche Spezifikation nicht verfügbar ist, besteht eine alternative Wahl für einen Benchmark darin, mit dem maximalen Lastniveau zu arbeiten, das auf der für den Test verfügbaren Hardware erreicht werden kann. Das mit diesem Ansatz verbundene Risiko besteht jedoch darin, dass die Benchmark-Ergebnisse im Kontext der Zielanwendung möglicherweise nicht relevant sind, wenn die erwarteten Belastungsniveaus nicht berücksichtigt werden.

In jedem Fall - insbesondere bei Benchmarks, bei denen die Lastniveaus von den Leistungsgrenzen der für den Test verfügbaren Hardware abhängen - sollten Sie sich darüber im Klaren sein, wie sich die Überlastung der Ressourcen auf die Testergebnisse auswirkt. Wenn Sie beispielsweise die Ausführungszeiten von Komponenten vergleichen, ist es im Allgemeinen ratsam, unter der CPU-Auslastung von 75-80% zu bleiben. Eine Erhöhung der Auslastung über diesen Wert hinaus kann die Genauigkeit der Messung der Testausführungszeit beeinträchtigen und die Benchmark-Ergebnisse verfälschen.

Oft wird der aggregierte Leistungstestparameter 'Lastniveau' nicht explizit in seine Hauptteile unterteilt: Intensität und Parallelität (siehe Abb. 3). Dies kann zu einer unzureichenden Konfiguration des Leistungstests führen.

Abb. 3: Die wichtigsten Parameter für Leistungstests: Intensität, Parallelität und Dauer

Belastungsintensität ist die Rate der Anforderungen oder Testaufrufe, mit denen die Komponente getestet wird. In einer Leistungstestanwendung kann die Lastintensität direkt konfiguriert werden, indem die Treffer / Test pro Sekunde / Minute / Stunde eines Leistungstestszenarios festgelegt werden. Es kann auch indirekt als Kombination aus der Anzahl der virtuellen Benutzer und der Denkzeit des virtuellen Benutzers konfiguriert werden.

Parallelität laden (in unserem Kontext) kann als der Grad der Parallelität beschrieben werden, mit dem eine Last einer bestimmten Intensität angelegt wird. Die Parallelitätsstufe kann anhand der Anzahl der virtuellen Benutzer oder Threads in einem Auslastungstestszenario konfiguriert werden. Das Festlegen einer geeigneten Parallelitätsstufe ist wichtig, wenn Sie auf potenzielle Rennbedingungen und Leistungsprobleme aufgrund von Thread-Konflikten und Zugriff auf gemeinsam genutzte Ressourcen testen sowie den Speicherbedarf mehrerer Instanzen der Komponente messen möchten.

Testdauer Für einen Komponenten-Benchmark-Test hängt dies von den Testzielen ab. Aus mathematischer Sicht ist die statistische Signifikanz des Lasttestdatensatzes einer der Hauptfaktoren bei der Bestimmung der Testdauer. Diese Art von Ansatz kann jedoch für alltägliche praktische Zwecke zu kompliziert sein. 

Um den Rest des Blogs zu lesen, sehen Sie sich das an Whitepaper hier.

Geschrieben von

Sergej Baranow

Sergei ist Principal Software Engineer bei Parasoft und konzentriert sich auf Last- und Leistungstests innerhalb von Parasoft SOAtest.

Erhalten Sie die neuesten Nachrichten und Ressourcen zum Testen von Software sofort.