Testmethoden von `test::nginx`: Konfiguration, Senden von Anfragen und Verarbeiten von Antworten
API7.ai
November 18, 2022
Im letzten Artikel haben wir bereits einen ersten Blick auf test::nginx
geworfen und das einfachste Beispiel ausgeführt. In einem echten Open-Source-Projekt sind die mit test::nginx
geschriebenen Testfälle jedoch viel komplexer und schwieriger zu beherrschen als der Beispielcode. Sonst wäre es kein Hindernis.
In diesem Artikel werde ich Sie durch die häufig verwendeten Befehle und Testmethoden in test::nginx
führen, damit Sie die meisten Testfallsätze im OpenResty-Projekt verstehen und in der Lage sind, realistischere Testfälle zu schreiben. Selbst wenn Sie noch keinen Code zu OpenResty beigetragen haben, wird die Vertrautheit mit dem Testframework von OpenResty eine große Inspiration für Sie sein, um Testfälle in Ihrer Arbeit zu entwerfen und zu schreiben.
Der test::nginx
-Test generiert im Wesentlichen eine nginx.conf
und startet einen NGINX-Prozess basierend auf der Konfiguration jedes Testfalls. Dann simuliert er eine Client-Anfrage mit dem angegebenen Anfragekörper und den Headern. Anschließend verarbeitet der Lua-Code im Testfall die Anfrage und gibt eine Antwort. Zu diesem Zeitpunkt analysiert test::nginx
die kritischen Informationen wie den Antwortkörper, die Antwortheader und die Fehlerprotokolle und vergleicht sie mit der Testkonfiguration. Wenn eine Diskrepanz besteht, schlägt der Test mit einem Fehler fehl; andernfalls ist er erfolgreich.
test::nginx
bietet viele DSL (Domain-spezifische Sprache)-Primitive. Ich habe eine einfache Klassifizierung vorgenommen, basierend auf der Konfiguration von NGINX, dem Senden von Anfragen, der Verarbeitung von Antworten und der Überprüfung von Protokollen. Diese 20 % der Funktionalität können 80 % der Anwendungsfälle abdecken, daher müssen wir sie fest im Griff haben. Andere fortgeschrittenere Primitive und Verwendungen werden wir im nächsten Artikel vorstellen.
NGINX-Konfiguration
Schauen wir uns zunächst die NGINX-Konfiguration an. Das Primitive von test::nginx
mit dem Schlüsselwort "config" bezieht sich auf die NGINX-Konfiguration, wie z.B. config
, stream_config
, http_config
usw.
Ihre Funktionen sind dieselben: Sie fügen die angegebene NGINX-Konfiguration in verschiedene NGINX-Kontexte ein. Diese Konfigurationen können entweder NGINX-Befehle oder in content_by_lua_block
gekapselter Lua-Code sein.
Bei Unit-Tests ist config
das am häufigsten verwendete Primitive, in dem wir Lua-Bibliotheken laden und Funktionen für White-Box-Tests aufrufen. Hier ist ein Testcode-Snippet, das nicht vollständig ausgeführt werden kann. Es stammt aus einem echten Open-Source-Projekt. Wenn Sie interessiert sind, können Sie auf den Link klicken, um den vollständigen Test zu sehen, oder Sie können versuchen, ihn lokal auszuführen.
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.key-auth")
local ok, err = plugin.check_schema({key = 'test-key'})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
Der Zweck dieses Testfalls ist es, zu testen, ob die Funktion check_schema
in der Codedatei plugins.key-auth
ordnungsgemäß funktioniert. Es verwendet den NGINX-Befehl content_by_lua_block
in location /t
, um das zu testende Modul zu laden und die zu überprüfende Funktion direkt aufzurufen.
Dies ist ein gängiges Mittel für White-Box-Tests in test::nginx
. Diese Konfiguration allein reicht jedoch nicht aus, um den Test abzuschließen. Lassen Sie uns also fortfahren und sehen, wie eine Client-Anfrage gesendet wird.
Anfragen senden
Das Simulieren eines Clients, der eine Anfrage sendet, beinhaltet viele Details. Beginnen wir mit dem einfachsten - dem Senden einer einzelnen Anfrage.
request
Fahren wir mit dem obigen Testfall fort. Wenn wir möchten, dass der Unit-Test-Code ausgeführt wird, müssen wir eine HTTP-Anfrage an die in der Konfiguration angegebene Adresse /t
senden, wie im folgenden Testcode gezeigt:
--- request
GET /t
Dieser Code sendet eine GET
-Anfrage an /t
im Anfrage-Primitive. Hier geben wir nicht die IP-Adresse, den Domainnamen oder den Port des Zugriffs an, noch geben wir an, ob es sich um HTTP 1.0
oder HTTP 1.1
handelt. All diese Details werden von test::nginx
verborgen, sodass wir uns nicht darum kümmern müssen. Dies ist einer der Vorteile von DSL - wir müssen uns nur auf die Geschäftslogik konzentrieren, ohne von allen Details abgelenkt zu werden.
Außerdem bietet dies teilweise Flexibilität. Zum Beispiel ist das Standardprotokoll HTTP 1.1
, oder wenn wir HTTP 1.0
testen möchten, können wir dies separat angeben:
--- request
GET /t HTTP/1.0
Neben der GET
-Methode muss auch die POST
-Methode unterstützt werden. Im folgenden Beispiel können wir die Zeichenkette hello world
an die angegebene Adresse POST
en.
--- request
POST /t
hello world
Auch hier berechnet test::nginx
die Länge des Anfragekörpers für Sie und fügt die host
- und connection
-Anfrageheader automatisch hinzu, um sicherzustellen, dass dies eine normale Anfrage ist.
Natürlich können wir Kommentare hinzufügen, um es lesbarer zu machen. Zeilen, die mit #
beginnen, werden als Codekommentare erkannt.
--- request
# post request
POST /t
hello world
Die Anfrage unterstützt auch einen komplexeren und flexibleren Modus, der eval
als Filter verwendet, um Perl-Code direkt einzubetten, da test::nginx
in Perl geschrieben ist. Wenn die aktuelle DSL-Sprache Ihre Anforderungen nicht erfüllt, ist eval
die "ultimative Waffe", um Perl-Code direkt auszuführen.
Für die Verwendung von eval
schauen wir uns hier ein paar einfache Beispiele an, und wir werden im nächsten Artikel mit anderen komplexeren fortfahren.
--- request eval
"POST /t
hello\x00\x01\x02
world\x03\x04\xff"
Im ersten Beispiel verwenden wir eval
, um nicht druckbare Zeichen anzugeben, was eine seiner Verwendungen ist. Der Inhalt zwischen den doppelten Anführungszeichen wird als Perl-String behandelt und dann als Argument an die request
übergeben.
Hier ist ein interessanteres Beispiel:
--- request eval
"POST /t\n" . "a" x 1024
Um dieses Beispiel zu verstehen, müssen wir jedoch etwas über Strings in Perl wissen, daher möchte ich hier kurz zwei Punkte erwähnen.
- In Perl verwenden wir einen Punkt, um String-Verkettung darzustellen. Ist das nicht ein bisschen ähnlich wie die zwei Punkte in
Lua
? - Ein kleines
x
gibt die Anzahl der Wiederholungen eines Zeichens an. Zum Beispiel bedeutet"a" x 1024
oben, dass das Zeichen "a" 1024 Mal wiederholt wird.
Das zweite Beispiel bedeutet also, dass die POST
-Methode eine Anfrage mit 1024
Zeichen a
an die Adresse /t
sendet.
pipelined_requests
Nachdem wir verstanden haben, wie eine einzelne Anfrage gesendet wird, schauen wir uns an, wie mehrere Anfragen gesendet werden. In test::nginx
können wir das Primitive pipelined_requests
verwenden, um mehrere Anfragen nacheinander innerhalb derselben keep-alive
-Verbindung zu senden:
--- pipelined_requests eval
["GET /hello", "GET /world", "GET /foo", "GET /bar"]
Dieses Beispiel greift beispielsweise nacheinander auf diese vier APIs in derselben Verbindung zu. Es gibt zwei Vorteile dabei:
- Der erste ist, dass viel repetitiver Testcode eliminiert werden kann und die vier Testfälle in einen komprimiert werden können.
- Der zweite und wichtigste Grund ist, dass wir mit pipelined requests feststellen können, ob die Codelogik bei mehrfachen Zugriffen Ausnahmen aufweist.
Sie fragen sich vielleicht, wenn ich mehrere Testfälle nacheinander schreibe, wird der Code dann nicht auch mehrmals in der Ausführungsphase ausgeführt? Deckt das nicht auch das zweite Problem oben ab?
Es kommt auf den Ausführungsmodus von test::nginx
an, der anders funktioniert, als Sie vielleicht denken. Nach jedem Testfall schaltet test::nginx
den aktuellen NGINX-Prozess ab, und alle Daten im Speicher verschwinden. Beim Ausführen des nächsten Testfalls wird die nginx.conf
neu generiert und ein neuer NGINX-Worker
gestartet. Dieser Mechanismus stellt sicher, dass sich Testfälle nicht gegenseitig beeinflussen.
Wenn wir also mehrere Anfragen testen möchten, müssen wir das Primitive pipelined_requests
verwenden. Basierend darauf können wir Szenarien wie Ratenbegrenzung, Parallelitätsbegrenzung und viele andere simulieren, um zu testen, ob Ihr System unter realistischeren und komplexeren Szenarien ordnungsgemäß funktioniert. Wir werden dies ebenfalls im näch Artikel behandeln, da es mehrere Befehle und Primitive beinhalten wird.
repeat_each
Wir haben gerade den Fall des Testens mehrerer Anfragen erwähnt. Wie sollten wir denselben Test mehrmals ausführen?
Für dieses Problem bietet test::nginx
eine globale Einstellung: repeat_each
, eine Perl-Funktion, die standardmäßig auf repeat_each(1)
gesetzt ist, was bedeutet, dass der Testfall nur einmal ausgeführt wird. Daher müssen wir in den vorherigen Testfällen dies nicht separat festlegen.
Natürlich können wir es vor der Funktion run_test()
festlegen, indem wir das Argument beispielsweise auf 2
ändern.
repeat_each(2);
run_tests();
Dann wird jeder Testfall zweimal ausgeführt, und so weiter.
more_headers
Nachdem wir über den Anfragekörper gesprochen haben, schauen wir uns die Anfrageheader an. Wie wir oben erwähnt haben, sendet test::nginx
die Anfrage standardmäßig mit den Headern host
und connection
. Was ist mit den anderen Anfrageheadern?
more_headers
ist speziell dafür gedacht.
--- more_headers
X-Foo: blah
Wir können es verwenden, um verschiedene benutzerdefinierte Header zu setzen. Wenn wir mehr als einen Header setzen möchten, setzen wir mehr als eine Zeile:
--- more_headers
X-Foo: 3
User-Agent: openresty
Antworten verarbeiten
Nach dem Senden der Anfrage ist der wichtigste Teil von test::nginx
die Verarbeitung der Antwort, wo wir feststellen, ob die Antwort den Erwartungen entspricht. Hier teilen wir es in vier Teile auf und stellen sie vor: den Antwortkörper, die Antwortheader, den Antwortstatuscode und das Protokoll.
response_body
Das Gegenstück zum Anfrage-Primitive ist response_body
, und das Folgende ist ein Beispiel für ihre beiden Konfigurationen in der Verwendung:
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
ngx.say("hello")
}
}
--- request
GET /t
--- response_body
hello
Dieser Testfall wird bestanden, wenn der Antwortkörper hello
ist, und wird in anderen Fällen einen Fehler melden. Aber wie testen wir einen langen Rückgabekörper? Machen Sie sich keine Sorgen, test::nginx
hat dies bereits für Sie erledigt. Es unterstützt die Erkennung des Antwortkörpers mit einem regulären Ausdruck, wie im Folgenden:
--- response_body_like
^he\w+$
Dies ermöglicht es Ihnen, sehr flexibel mit dem Antwortkörper umzugehen. Darüber hinaus unterstützt test::nginx
auch unlike
-Operationen:
--- response_body_unlike
^he\w+$
An diesem Punkt wird der Test nicht bestanden, wenn der Antwortkörper hello
ist.
In der gleichen Weise, nachdem wir die Erkennung einer einzelnen Anfrage verstanden haben, schauen wir uns die Erkennung mehrerer Anfragen an. Hier ist ein Beispiel, wie es mit pipelined_requests
verwendet wird:
--- pipelined_requests eval
["GET /hello", "GET /world", "GET /foo", "GET /bar"]
--- response_body eval
["hello", "world", "oo", "bar"]
Natürlich ist hier wichtig zu beachten, dass Sie so viele Antworten benötigen, wie Sie Anfragen senden.
response_headers
Zweitens sprechen wir über die Antwortheader. Die Antwortheader ähneln den Anfrageheadern, da jede Zeile dem Schlüssel und Wert eines Headers entspricht.
--- response_headers
X-RateLimit-Limit: 2
X-RateLimit-Remaining: 1
Ähnlich wie bei der Erkennung des Antwortkörpers unterstützen Antwortheader auch reguläre Ausdrücke und unlike
-Operationen, wie response_headers_like
, raw_response_headers_like
und raw_response_headers_unlike
.
error_code
Der dritte ist der Antwortcode. Die Erkennung des Antwortcodes unterstützt den direkten Vergleich und auch like
-Operationen, wie die folgenden beiden Beispiele:
--- error_code: 302
--- error_code_like: ^(?:500)?$
Im Fall mehrerer Anfragen muss der error_code
mehrmals überprüft werden:
--- pipelined_requests eval
["GET /hello", "GET /hello", "GET /hello", "GET /hello"]
--- error_code eval
[200, 200, 503, 503]
error_log
Der letzte Testpunkt ist das Fehlerprotokoll. In den meisten Testfällen wird kein Fehlerprotokoll generiert. Wir können no_error_log
zur Erkennung verwenden:
--- no_error_log
[error]
Im obigen Beispiel schlägt der Test fehl, wenn die Zeichenkette [error]
im NGINX error.log
erscheint. Dies ist eine sehr häufige Funktion, und es wird empfohlen, dass Sie die Erkennung des Fehlerprotokolls zu allen Ihren normalen Tests hinzufügen.
--- error_log
hello world
Die obige Konfiguration erkennt das Vorhandensein von hello world
in error.log
. Natürlich können Sie eval
verwenden, um Perl-Code einzubetten und reguläre Ausdrücke zur Erkennung zu implementieren, wie im Folgenden:
--- error_log eval
qr/\[notice\] .*? \d+ hello world/
Zusammenfassung
Heute haben wir gelernt, wie man Anfragen sendet und Antworten in test::nginx
testet, einschließlich des Anfragekörpers, der Header, des Antwortstatuscodes und des Fehlerprotokolls. Mit der Kombination dieser Primitive können wir einen vollständigen Satz von Testfällen implementieren.
Abschließend hier eine Denkaufgabe: Was sind die Vor- und Nachteile von test::nginx
, einer abstrakten DSL? Hinterlassen Sie gerne Kommentare und diskutieren Sie mit mir, und Sie sind auch eingeladen, diesen Artikel zu teilen, um gemeinsam zu kommunizieren und nachzudenken.