Obstacle à la contribution de code : `test::nginx`
API7.ai
November 17, 2022
Le test est une partie essentielle du développement logiciel. Le concept de développement piloté par les tests (TDD) est devenu si populaire que presque toutes les entreprises de logiciels disposent d'une équipe QA (Assurance Qualité) pour s'occuper des tests.
Le test est la pierre angulaire de la qualité et de la grande réputation d'OpenResty, mais c'est aussi la partie la plus négligée des projets open source d'OpenResty. De nombreux développeurs utilisent le module lua-nginx-module
quotidiennement et exécutent occasionnellement un graphique de flamme, mais combien de personnes exécutent les cas de test ? Même de nombreux projets open source basés sur OpenResty n'ont pas de cas de test. Mais un projet open source sans cas de test et sans intégration continue n'est pas digne de confiance.
Cependant, contrairement aux entreprises commerciales, il n'y a pas d'ingénieurs de test logiciel dédiés dans la plupart des projets open source. Alors, comment assurent-ils la qualité de leur code ? La réponse est simple : "automatisation des tests" et "intégration continue", les points clés étant l'automatisation et la continuité, deux aspects qu'OpenResty a atteints au maximum.
OpenResty compte 70 projets open source, et leurs tests unitaires, tests d'intégration, tests de performance, tests de simulation, tests de fuzz, et autres charges de travail sont difficiles à résoudre manuellement par les contributeurs de la communauté. Par conséquent, OpenResty a investi davantage dans les tests automatisés dès le début. Cela peut sembler ralentir le projet à court terme, mais on peut dire que l'investissement dans ce domaine est très rentable à long terme. Ainsi, lorsque je discute avec d'autres ingénieurs de la logique et de l'ensemble d'outils de test d'OpenResty, ils sont émerveillés.
Parlons de la philosophie de test d'OpenResty.
Concept
test::nginx
est le cœur de l'architecture de test d'OpenResty, utilisé par OpenResty lui-même et les bibliothèques environnantes lua-resty
pour organiser et écrire des ensembles de tests. C'est un framework de test avec un seuil très élevé. La raison en est que, contrairement aux frameworks de test courants, test::nginx
ne repose pas sur des assertions et n'utilise pas le langage Lua, ce qui oblige les développeurs à apprendre et à utiliser test::nginx
à partir de zéro et à inverser leurs connaissances inhérentes des frameworks de test.
Je connais plusieurs contributeurs d'OpenResty qui peuvent soumettre du code C et Lua à OpenResty mais qui trouvent difficile d'écrire des cas de test avec test::nginx
. Ils ne savaient pas comment les écrire ou comment les corriger en cas d'échec des tests. Par conséquent, j'appelle test::nginx
un obstacle à la contribution de code.
test::nginx
combine Perl, le pilotage par les données et DSL (langage spécifique à un domaine). Pour le même ensemble de cas de test, en contrôlant les paramètres et les variables d'environnement, vous pouvez obtenir différents effets tels que l'exécution aléatoire, les répétitions multiples, la détection de fuites de mémoire, les tests de stress, etc.
Installation et exemples
Avant d'utiliser test::nginx
, apprenons comment l'installer.
En ce qui concerne l'installation de logiciels dans le système OpenResty, seule la méthode d'installation officielle CI est la plus rapide et efficace ; les autres méthodes d'installation rencontrent toujours divers problèmes. C'est pourquoi je vous recommande de prendre les méthodes officielles comme référence, où vous pouvez également trouver l'installation et l'utilisation de test::nginx
. Il y a quatre étapes.
- Tout d'abord, installez le gestionnaire de paquets Perl
cpanminus
. - Ensuite, installez
test::nginx
viacpanm
.
sudo cpanm --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1)
- Ensuite, clonez le code source le plus récent.
git clone https://github.com/openresty/test-nginx.git
- Enfin, chargez la bibliothèque
test-nginx
via la commande Perlprove
et exécutez l'ensemble de cas de test dans le répertoire/t
.
prove -Itest-nginx/lib -r t
Après l'installation, examinons le cas de test le plus simple dans test::nginx
. Le code suivant est adapté de la documentation officielle, et j'ai supprimé tous les paramètres de contrôle personnalisés.
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
Bien que test::nginx
soit écrit en Perl et fonctionne comme l'un des modules, pouvez-vous voir quelque chose en Perl ou dans un autre langage dans le test ci-dessus ? C'est exact. C'est parce que test::nginx
est l'implémentation Perl DSL de l'auteur, abstraite spécifiquement pour tester NGINX et OpenResty.
Ainsi, lorsque nous voyons ce genre de test pour la première fois, nous ne comprenons probablement pas. Mais ne vous inquiétez pas ; analysons le cas de test ci-dessus.
Tout d'abord, use Test::Nginx::Socket;
, qui est la manière dont Perl référence les bibliothèques, tout comme le require
en Lua. Cela nous rappelle également que test::nginx
est un programme Perl.
La deuxième ligne, run_tests();
est une fonction Perl dans test::nginx
, la fonction d'entrée du framework de test. Si vous souhaitez appeler d'autres fonctions Perl dans test::nginx
, elles doivent être placées avant run_tests
pour être valides.
Le __DATA__
dans la troisième ligne est un indicateur indiquant que tout ce qui suit est des données de test, et les fonctions Perl doivent être terminées avant cet indicateur.
Le prochain === TEST 1: set Server
, le titre du cas de test, indique l'objectif de ce test, et il a un outil qui organise automatiquement la numérotation à l'intérieur.
--- config
est le champ de configuration NGINX. Dans le cas ci-dessus, nous avons utilisé des commandes NGINX, pas Lua, et si vous souhaitez ajouter du code Lua, vous le ferez ici avec une directive comme content_by_lua
.
--- request
est utilisé pour simuler un terminal pour envoyer une requête, suivi de GET /foo
, qui spécifie la méthode et l'URI de la requête.
--- response_headers
, qui est utilisé pour détecter les en-têtes de réponse. Le Server: Foo
suivant indique l'en-tête et la valeur qui doivent apparaître dans les en-têtes de réponse. Si ce n'est pas le cas, le test échouera.
Le dernier --- response_body
, est utilisé pour détecter le corps de réponse correspondant. Le hi
suivant est la chaîne qui doit apparaître dans le corps de la réponse ; si ce n'est pas le cas, le test échouera.
Eh bien, ici, le cas de test le plus simple est terminé. Ainsi, comprendre le cas de test est une condition préalable pour accomplir le travail de développement lié à OpenResty.
Écrivez vos cas de test
Ensuite, il est temps de passer aux tests pratiques. Vous souvenez-vous comment nous avons testé le serveur Memcached dans le dernier article ? C'est exact ; nous avons utilisé resty
pour envoyer la requête manuellement, ce qui est représenté par le code suivant.
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)'
Mais envoyer manuellement n'est pas très intelligent, n'est-ce pas ? Pas de soucis. Nous pouvons essayer de transformer les tests manuels en tests automatisés après avoir appris test::nginx
. Par exemple :
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]
Dans ce cas de test, j'ai ajouté --- stream_config
, --- stream_server_config
, --- no_error_log
comme éléments de configuration, mais ils sont essentiellement les mêmes, c'est-à-dire.
Les données et les tests des tests sont simplifiés pour améliorer la lisibilité et l'extensibilité en abstraisant la configuration.
C'est là que test::nginx
est fondamentalement différent des autres frameworks de test. Ce DSL est une épée à double tranchant en ce qu'il rend la logique de test claire et facilement extensible. Cependant, il augmente le coût d'apprentissage, vous obligeant à réapprendre une nouvelle syntaxe et configuration avant de pouvoir commencer à écrire des cas de test.
Résumé
Le test::nginx
est puissant, mais souvent il peut ne pas toujours convenir à votre scénario. Pourquoi utiliser un marteau-pilon pour écraser une mouche ? Dans OpenResty, vous avez également la possibilité d'utiliser le framework de test basé sur les assertions busted
. Le busted
combiné avec resty
devient un outil en ligne de commande, et peut également répondre à de nombreux besoins de test.
Enfin, je vous laisse avec une question. Pouvez-vous exécuter ce test pour Memcached
localement ? Si vous pouvez ajouter un nouveau cas de test, ce serait formidable.