Hindernis beim Code-Beitrag: `test::nginx`

API7.ai

November 17, 2022

OpenResty (NGINX + Lua)

Testing ist ein wesentlicher Bestandteil der Softwareentwicklung. Das Konzept der Testgetriebenen Entwicklung (TDD) ist so populär geworden, dass fast jedes Softwareunternehmen ein QA-Team (Quality Assurance) hat, das sich um die Testarbeit kümmert.

Testing ist der Eckpfeiler der Qualität und des guten Rufs von OpenResty, aber es ist auch der am meisten vernachlässigte Teil der Open-Source-Projekte von OpenResty. Viele Entwickler verwenden das lua-nginx-module täglich und führen gelegentlich ein Flame Graph aus, aber wie viele Leute führen die Testfälle aus? Sogar viele OpenResty-basierte Open-Source-Projekte haben keine Testfälle. Aber ein Open-Source-Projekt ohne Testfälle und kontinuierliche Integration ist nicht vertrauenswürdig.

Im Gegensatz zu kommerziellen Unternehmen gibt es jedoch in den meisten Open-Source-Projekten keine spezialisierten Softwaretestingenieure. Wie stellen sie also die Qualität ihres Codes sicher? Die Antwort ist einfach: "Testautomatisierung" und "kontinuierliche Integration", wobei die Schlüsselpunkte Automatisierung und Kontinuität sind, beides hat OpenResty in größtmöglichem Umfang erreicht.

OpenResty hat 70 Open-Source-Projekte, und ihre Unit-Tests, Integrationstests, Leistungstests, Mock-Tests, Fuzz-Tests und andere Arbeitslasten sind schwer zu lösen, wenn sie rein manuell von den Community-Mitwirkenden durchgeführt werden. Daher hat OpenResty von Anfang an mehr in die Automatisierungstests investiert. Dies mag kurzfristig das Projekt verlangsamen, aber langfristig ist die Investition in diesem Bereich sehr kosteneffektiv. Wenn ich also mit anderen Ingenieuren über die Logik und das Toolset von OpenResty für Tests spreche, sind sie erstaunt.

Lassen Sie uns über die Testphilosophie von OpenResty sprechen.

Konzept

test::nginx ist das Kernstück der OpenResty-Testarchitektur, das von OpenResty selbst und den umliegenden lua-resty-Bibliotheken verwendet wird, um Testsets zu organisieren und zu schreiben. Es ist ein Testframework mit einer sehr hohen Einstiegshürde. Der Grund dafür ist, dass test::nginx im Gegensatz zu gängigen Testframeworks nicht auf Assertions basiert und nicht die Lua-Sprache verwendet, was Entwickler dazu zwingt, test::nginx von Grund auf zu lernen und ihr vorhandenes Wissen über Testframeworks umzukehren.

Ich kenne mehrere OpenResty-Mitwirkende, die C- und Lua-Code an OpenResty übermitteln können, aber es schwierig finden, Testfälle mit test::nginx zu schreiben. Sie wussten entweder nicht, wie man sie schreibt, oder wie man sie repariert, wenn sie auf Testfehler stoßen. Daher bezeichne ich test::nginx als ein Hindernis bei der Code-Beitrag.

test::nginx kombiniert Perl, datengetriebene und DSL (Domain-specific language). Für denselben Testfall-Satz können Sie durch die Steuerung der Parameter und Umgebungsvariablen unterschiedliche Effekte wie zufällige Ausführung, mehrfache Wiederholungen, Speicherleckerkennung, Stresstests usw. erreichen.

Installation und Beispiele

Bevor wir test::nginx verwenden, lernen wir, wie man es installiert.

Was die Softwareinstallation im OpenResty-System betrifft, ist nur die offizielle CI-Installationsmethode die zeitnahste und effektivste; andere Installationsmethoden stoßen immer auf verschiedene Probleme. Deshalb empfehle ich Ihnen, offizielle Methoden als Referenz zu nehmen, wo Sie auch die Installation und Verwendung von test::nginx finden. Es gibt vier Schritte.

  1. Installieren Sie zunächst den Perl-Paketmanager cpanminus.
  2. Installieren Sie dann test::nginx über cpanm.
sudo cpanm --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1)
  1. Klonen Sie als nächstes den neuesten Quellcode.
git clone https://github.com/openresty/test-nginx.git
  1. Laden Sie schließlich die test-nginx-Bibliothek über den Perl-Befehl prove und führen Sie den Satz von Testfällen im Verzeichnis /t aus.
prove -Itest-nginx/lib -r t

Nach der Installation schauen wir uns den einfachsten Testfall in test::nginx an. Der folgende Code ist an die offizielle Dokumentation angepasst, und ich habe alle angepassten Steuerparameter entfernt.

use Test::Nginx::Socket 'no_plan';


run_tests();

__DATA__

=== TEST 1: set Server
--- config
    location /foo {
        echo hi;
        more_set_headers 'Server: Foo';
    }
--- request
    GET /foo
--- response_headers
Server: Foo
--- response_body
hi

Obwohl test::nginx in Perl geschrieben ist und als eines der Module funktioniert, können Sie etwas in Perl oder einer anderen Sprache aus dem obigen Test erkennen? Richtig. Es liegt daran, dass test::nginx die eigene Perl-Implementierung von DSL des Autors ist, speziell für das Testen von NGINX und OpenResty abstrahiert.

Wenn wir also zum ersten Mal diese Art von Test sehen, verstehen wir ihn höchstwahrscheinlich nicht. Aber keine Sorge; lassen Sie uns den obigen Testfall analysieren.

Zunächst einmal, use Test::Nginx::Socket;, das ist die Art und Weise, wie Perl Bibliotheken referenziert, genau wie require in Lua. Dies erinnert uns auch daran, dass test::nginx ein Perl-Programm ist.

Die zweite Zeile, run_tests(); ist eine Perl-Funktion in test::nginx, die Einstiegsfunktion für das Testframework. Wenn Sie andere Perl-Funktionen in test::nginx aufrufen möchten, müssen sie vor run_tests platziert werden, um gültig zu sein.

Das __DATA__ in der dritten Zeile ist ein Flag, das anzeigt, dass alles darunter Testdaten sind, und Perl-Funktionen sollten vor diesem Flag abgeschlossen sein.

Das nächste === TEST 1: set Server, der Titel des Testfalls, gibt den Zweck dieses Tests an, und es gibt ein Tool, das die Nummerierung automatisch arrangiert.

--- config ist das NGINX-Konfigurationsfeld. Im obigen Fall haben wir NGINX-Befehle verwendet, nicht Lua, und wenn Sie Lua-Code hinzufügen möchten, würden Sie dies hier mit einer Direktive wie content_by_lua tun.

--- request wird verwendet, um ein Terminal zu simulieren, das eine Anfrage sendet, gefolgt von GET /foo, das die Methode und den URI der Anfrage angibt.

--- response_headers, das wird verwendet, um Antwortheader zu überprüfen. Das folgende Server: Foo gibt den header und value an, die in den Antwortheadern erscheinen müssen. Wenn nicht, schlägt der Test fehl.

Der letzte --- response_body, wird verwendet, um den entsprechenden Body zu überprüfen. Das folgende hi ist die Zeichenkette, die im Antwortbody erscheinen muss; wenn nicht, schlägt der Test fehl.

Nun, hier ist die Analyse des einfachsten Testfalls abgeschlossen. Das Verständnis des Testfalls ist also eine Voraussetzung für die Erledigung von OpenResty-bezogenen Entwicklungsarbeiten.

Schreiben Sie Ihre Testfälle

Als nächstes ist es an der Zeit, mit dem praktischen Testen zu beginnen. Erinnern Sie sich, wie wir im letzten Artikel den Memcached-Server getestet haben? Richtig; wir haben resty verwendet, um die Anfrage manuell zu senden, was durch den folgenden Code dargestellt wird.

resty -e 'local memcached = require "resty.memcached"
    local memc, err = memcached:new()

    memc:set_timeout(1000) -- 1 sec
    local ok, err = memc:connect("127.0.0.1", 11212)
    local ok, err = memc:set("dog", 32)
    if not ok then
        ngx.say("failed to set dog: ", err)
        return
    end

    local res, flags, err = memc:get("dog")
    ngx.say("dog: ", res)'

Aber ist das manuelle Senden nicht intelligent genug? Keine Sorge. Wir können versuchen, manuelle Tests in automatisierte umzuwandeln, nachdem wir test::nginx gelernt haben. Zum Beispiel:

use Test::Nginx::Socket::Lua::Stream;

run_tests();

__DATA__
  
=== TEST 1: basic get and set
--- config
        location /test {
            content_by_lua_block {
                local memcached = require "resty.memcached"
                local memc, err = memcached:new()
                if not memc then
                    ngx.say("failed to instantiate memc: ", err)
                    return
                end

                memc:set_timeout(1000) -- 1 sec
                local ok, err = memc:connect("127.0.0.1", 11212)

                local ok, err = memc:set("dog", 32)
                if not ok then
                    ngx.say("failed to set dog: ", err)
                    return
                end

                local res, flags, err = memc:get("dog")
                ngx.say("dog: ", res)
            }
        }

--- stream_config
    lua_shared_dict memcached 100m;

--- stream_server_config
    listen 11212;
    content_by_lua_block {
        local m = require("memcached-server")
        m.go()
    }

--- request
GET /test
--- response_body
dog: 32
--- no_error_log
[error]

In diesem Testfall habe ich --- stream_config, --- stream_server_config, --- no_error_log als Konfigurationselemente hinzugefügt, aber sie sind im Wesentlichen gleich, d.h.

Die Daten und Tests der Tests werden abgestrippt, um die Lesbarkeit und Erweiterbarkeit durch die Abstraktion der Konfiguration zu verbessern.

Hier unterscheidet sich test::nginx grundlegend von anderen Testframeworks. Diese DSL ist ein zweischneidiges Schwert, da sie die Testlogik klar und leicht erweiterbar macht. Sie erhöht jedoch die Lernkosten, da Sie neue Syntax und Konfiguration neu lernen müssen, bevor Sie mit dem Schreiben von Testfällen beginnen können.

Zusammenfassung

Das test::nginx ist leistungsstark, aber oft passt es nicht immer zu Ihrem Szenario. Warum mit Kanonen auf Spatzen schießen? In OpenResty haben Sie auch die Möglichkeit, das Assertion-basierte Testframework busted zu verwenden. Das busted kombiniert mit resty wird zu einem Befehlszeilentool und kann auch viele Testanforderungen erfüllen.

Zum Schluss stelle ich Ihnen eine Frage. Können Sie diesen Test für Memcached lokal ausführen? Wenn Sie einen neuen Testfall hinzufügen können, wäre das großartig.