Utilisations méconnues de `test::nginx`
API7.ai
November 24, 2022
Dans les deux articles précédents, vous avez maîtrisé la plupart des utilisations de test::nginx
, et je crois que vous pouvez comprendre la majorité des ensembles de cas de test dans le projet OpenResty. Cela est plus que suffisant pour apprendre OpenResty et ses bibliothèques environnantes.
Mais si vous êtes intéressé à devenir un contributeur de code OpenResty, ou si vous utilisez test::nginx
pour écrire des cas de test dans vos projets, alors vous devez apprendre des utilisations plus avancées et complexes.
L'article d'aujourd'hui sera probablement la partie la plus "impopulaire" de la série car c'est quelque chose que personne n'a jamais partagé auparavant. Prenons lua-nginx-module
, le module central d'OpenResty, comme exemple, qui compte plus de 70 contributeurs dans le monde, mais tous les contributeurs n'ont pas écrit un cas de test. Donc, si vous lisez l'article d'aujourd'hui, votre compréhension de test::nginx
entrera dans le Top 100 mondial.
Débogage dans les tests
Tout d'abord, examinons certaines des sections les plus simples et les plus couramment utilisées par les développeurs lors du débogage normal. Ici, nous allons introduire les scénarios d'utilisation de ces sections liées au débogage une par une.
ONLY
Très souvent, nous ajoutons un nouveau cas de test à l'ensemble original de cas de test. Si le fichier de test contient beaucoup de cas de test, il est chronophage de tout exécuter, surtout lorsque vous devez modifier les cas de test à plusieurs reprises.
Alors, existe-t-il un moyen d'exécuter uniquement l'un des cas de test que vous spécifiez ? Cela peut facilement être fait avec la section ONLY
.
=== TEST 1: sanity
=== TEST 2: get
--- ONLY
Le pseudocode ci-dessus montre comment utiliser cette section. En plaçant --- ONLY
dans la dernière ligne du cas de test qui doit être exécuté seul, alors lorsque vous utilisez prove
pour exécuter le fichier de cas de test, tous les autres cas de test seront ignorés, et seul ce test sera exécuté.
Cependant, cela n'est approprié que lorsque vous faites du débogage. Ainsi, lorsque la commande prove
trouve la section ONLY
, elle vous rappellera également de ne pas oublier de la supprimer lorsque vous committerez votre code.
SKIP
La demande correspondant à l'exécution d'un seul cas de test est d'ignorer un cas de test particulier. La section SKIP
, qui est généralement utilisée pour tester une fonctionnalité qui n'a pas encore été implémentée :
=== TEST 1: sanity
=== TEST 2: get
--- SKIP
Comme vous pouvez le voir dans ce pseudocode, son utilisation est similaire à ONLY
. Parce que nous faisons du développement piloté par les tests, nous devons d'abord écrire des cas de test ; et lorsque nous codons l'implémentation collectivement, nous pouvons avoir besoin de retarder l'implémentation d'une fonctionnalité en raison de la difficulté ou de la priorité de l'implémentation. Ensuite, vous pouvez sauter l'ensemble de cas de test correspondant d'abord, puis supprimer la section SKIP
lorsque l'implémentation est terminée.
LAST
Une autre section courante est LAST
, qui est également simple à utiliser, car les cas de test avant elle seront exécutés et ceux après seront ignorés.
=== TEST 1: sanity
=== TEST 2: get
--- LAST
=== TEST 3: set
Vous pourriez vous demander, je peux comprendre l'importance de ONLY
et SKIP
, mais à quoi sert LAST
? En fait, parfois vos cas de test ont des dépendances, et vous devez exécuter les premiers cas de test avant que les tests suivants n'aient un sens. Donc, dans ce cas, LAST
est très utile lorsque vous continuez à déboguer.
plan
De toutes les fonctions de test::nginx
, plan
est l'une des plus frustrantes et difficiles à comprendre. Elle est dérivée du module Perl Test::Plan
, dont la documentation n'est pas dans test::nginx
, et trouver une explication à ce sujet n'est pas facile. Par conséquent, je vais l'introduire tôt. J'ai vu plusieurs contributeurs de code OpenResty qui sont tombés dans ce piège et n'ont même pas pu en sortir.
Voici un exemple de configuration similaire que vous pouvez voir au début de chaque fichier dans l'ensemble de tests officiel d'OpenResty :
plan tests => repeat_each() * (3 * blocks());
La signification de plan
ici est combien de tests devraient être effectués selon le plan dans l'ensemble du fichier de test. Si le résultat de l'exécution finale ne correspond pas au plan, le test échouera.
Pour cet exemple, si la valeur de repeat_each
est 2
et qu'il y a 10 cas de test, alors la valeur de plan
devrait être 2 x 3 x 10 = 60
. La seule chose qui peut vous sembler confuse est la signification du nombre 3
, qui ressemble à un nombre magique !
Ne vous inquiétez pas, continuons à regarder l'exemple, vous comprendrez dans un instant. Tout d'abord, pouvez-vous déterminer quelle est la valeur correcte de plan dans le cas de test suivant ?
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
ngx.say("hello")
}
}
--- request
GET /t
--- response_body
hello
Je crois que tout le monde conclurait que plan = 1
, puisque le test ne vérifie que le response_body
.
Mais ce n'est pas le cas ! La bonne réponse est que plan = 2
. Pourquoi ? Parce que test::nginx
a une vérification implicite, c'est-à-dire --- error_code: 200
, qui détecte si le code de réponse HTTP est 200
par défaut.
Ainsi, le nombre magique 3
ci-dessus signifie vraiment que chaque test est explicitement vérifié deux fois, par exemple pour body
et error log
, et implicitement pour response code
.
Comme cela est si sujet aux erreurs, je vous recommande de désactiver plan
en utilisant la méthode suivante.
use Test::Nginx::Socket 'no_plan';
Si vous ne pouvez pas le désactiver, par exemple, si vous rencontrez un plan
inexact dans l'ensemble de tests officiel d'OpenResty, il est recommandé de ne pas approfondir la cause, mais simplement d'ajouter ou de soustraire des nombres aux expressions de plan
.
plan tests => repeat_each() * (3 * blocks()) + 2;
C'est également la méthode officielle qui sera utilisée.
Préprocesseur
Nous savons qu'il peut y avoir des paramètres publics entre différents cas de test du même fichier de test. Si les paramètres sont répétés dans chaque cas de test, cela rendra le code redondant et difficile à modifier plus tard.
À ce moment-là, vous pouvez utiliser la directive add_block_preprocessor
pour ajouter un morceau de code Perl, comme suit :
add_block_preprocessor(sub {
my $block = shift;
if (!defined $block->config) {
$block->set_value("config", <<'_END_');
location = /t {
echo $arg_a;
}
_END_
}
});
Ce préprocesseur ajoute une section config
à tous les cas de test, et le contenu est location /t
, de sorte que dans vos cas de test ultérieurs, vous pouvez omettre le config
et y accéder directement.
=== TEST 1:
--- request
GET /t?a=3
--- response_body
3
=== TEST 2:
--- request
GET /t?a=blah
--- response_body
blah
Fonctions personnalisées
En plus d'ajouter du code Perl au préprocesseur, vous pouvez également ajouter des fonctions Perl arbitraires, ou des fonctions personnalisées comme nous les appelons, avant la fonction run_tests
.
Voici un exemple qui ajoute une fonction qui lit un fichier et la combine avec la directive eval
pour implémenter un POST
de fichier :
sub read_file {
my $infile = shift;
open my $in, $infile
or die "cannot open $infile for reading: $!";
my $content = do { local $/; <$in> };
close $in;
$content;
}
our $CONTENT = read_file("t/test.jpg");
run_tests;
__DATA__
=== TEST 1: sanity
--- request eval
"POST /\n$::CONTENT"
Mélange
En plus de ce qui précède, test::nginx
a un piège peu connu : il exécute les cas de test dans un ordre aléatoire par défaut, au lieu de suivre l'ordre et la numérotation des cas de test.
Cela était initialement destiné à tester plus de problèmes. Après tout, après chaque cas de test exécuté, le processus NGINX est fermé, et un nouveau processus NGINX est démarré pour l'exécuter, donc les résultats ne devraient pas être liés à l'ordre.
Pour les projets de niveau inférieur, c'est vrai. Cependant, pour les projets de niveau application, il existe un stockage persistant tel que des bases de données à l'extérieur. Une exécution désordonnée peut conduire à des résultats erronés. Comme c'est aléatoire à chaque fois, cela peut ou non signaler une erreur, et l'erreur peut être différente à chaque fois. Cela cause évidemment de la confusion pour les développeurs, y compris moi, car je suis tombé ici plusieurs fois.
Donc, mon conseil est : veuillez désactiver cette fonctionnalité. Vous pouvez la désactiver avec les deux lignes de code suivantes :
no_shuffle();
run_tests;
En particulier, no_shuffle
est utilisé pour désactiver la randomisation et permet aux tests de s'exécuter strictement dans l'ordre des cas de test.
reindex
L'ensemble de cas de test d'OpenResty a des exigences de formatage strictes. Chaque cas de test doit être séparé par trois nouvelles lignes, et la numérotation des cas de test doit être strictement auto-croissante.
Heureusement, nous avons un outil automatique, reindex
, pour faire ce travail fastidieux, qui est caché dans le projet openresty-devel-utils. Comme il n'y a pas de documentation à ce sujet, seulement quelques personnes le connaissent.
Si vous êtes intéressé, vous pouvez essayer de désorganiser la numérotation des cas de test, ou ajouter ou supprimer le nombre de sauts de ligne, puis utiliser cet outil pour tout remettre en ordre et voir si vous pouvez le restaurer.
Résumé
C'est la fin de l'introduction à test::nginx
. Bien sûr, il y a plus de fonctions, nous n'avons parlé que des plus centrales et importantes. "Donnez un poisson à un homme, vous le nourrissez pour un jour ; apprenez-lui à pêcher, vous le nourrissez pour la vie." Je vous ai enseigné les méthodes de base et les précautions pour apprendre les tests, alors vous pouvez approfondir l'ensemble de cas de test officiel pour une meilleure compréhension.
Enfin, pensez aux questions suivantes. Y a-t-il des tests dans le développement de votre projet ? Et quel cadre utilisez-vous pour tester ? N'hésitez pas à partager cet article avec plus de personnes pour échanger et apprendre ensemble.