Obstáculo en la Contribución de Código: `test::nginx`
API7.ai
November 17, 2022
Las pruebas son una parte esencial del desarrollo de software. El concepto de Desarrollo Guiado por Pruebas (TDD, por sus siglas en inglés) se ha vuelto tan popular que casi todas las empresas de software tienen un equipo de QA (Garantía de Calidad) para encargarse del trabajo de pruebas.
Las pruebas son la piedra angular de la calidad y la gran reputación de OpenResty, pero también son la parte más descuidada de los proyectos de código abierto de OpenResty. Muchos desarrolladores usan el lua-nginx-module
a diario y ocasionalmente ejecutan un gráfico de llamadas (flame graph), pero ¿cuántas personas ejecutan los casos de prueba? Incluso muchos proyectos de código abierto basados en OpenResty carecen de casos de prueba. Pero un proyecto de código abierto sin casos de prueba e integración continua no es confiable.
Sin embargo, a diferencia de las empresas comerciales, en la mayoría de los proyectos de código abierto no hay ingenieros dedicados a las pruebas de software, entonces, ¿cómo aseguran la calidad de su código? La respuesta es simple: "automatización de pruebas" e "integración continua", siendo los puntos clave la automatización y la continuidad, ambos logrados por OpenResty en la mayor medida posible.
OpenResty tiene 70 proyectos de código abierto, y sus pruebas unitarias, de integración, de rendimiento, de simulación (mock), de fuzz y otras cargas de trabajo son difíciles de resolver manualmente por los contribuyentes de la comunidad. Por lo tanto, OpenResty invirtió más en pruebas automatizadas desde el principio. Esto puede parecer que ralentiza el proyecto a corto plazo, pero se puede decir que la inversión en esta área es muy rentable a largo plazo. Así que cuando hablo con otros ingenieros sobre la lógica y el conjunto de herramientas de pruebas de OpenResty, se quedan asombrados.
Hablemos de la filosofía de pruebas de OpenResty.
Concepto
test::nginx
es el núcleo de la arquitectura de pruebas de OpenResty, que es utilizada por OpenResty mismo y las bibliotecas circundantes lua-resty
para organizar y escribir conjuntos de pruebas. Es un marco de pruebas con un umbral muy alto. La razón es que, a diferencia de los marcos de pruebas comunes, test::nginx
no se basa en aserciones y no utiliza el lenguaje Lua, lo que requiere que los desarrolladores aprendan y usen test::nginx
desde cero y reviertan su conocimiento inherente de los marcos de pruebas.
Conozco a varios contribuyentes de OpenResty que pueden enviar código C y Lua a OpenResty, pero sienten que es difícil escribir casos de prueba usando test::nginx
. O no sabían cómo escribirlos o cómo solucionarlos cuando encontraban fallos en las pruebas. Por lo tanto, llamo a test::nginx
un obstáculo en la contribución de código.
test::nginx
combina Perl, datos impulsados y DSL (Lenguaje específico del dominio). Para el mismo conjunto de casos de prueba, al controlar los parámetros y las variables de entorno, se pueden lograr diferentes efectos como ejecución aleatoria, múltiples repeticiones, detección de fugas de memoria, pruebas de estrés, etc.
Instalación y ejemplos
Antes de usar test::nginx
, aprendamos cómo instalarlo.
En cuanto a la instalación de software en el sistema OpenResty, solo el método de instalación oficial de CI es el más oportuno y efectivo; otras formas de instalación siempre encuentran varios problemas. Por eso recomiendo que tomen los métodos oficiales como referencia, donde también pueden encontrar la instalación y el uso de test::nginx
. Hay cuatro pasos.
- Primero, instale el gestor de paquetes de Perl
cpanminus
. - Luego, instale
test::nginx
a través decpanm
.
sudo cpanm --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1)
- A continuación, clone el código fuente más reciente.
git clone https://github.com/openresty/test-nginx.git
- Finalmente, cargue la biblioteca
test-nginx
a través del comandoprove
de Perl y ejecute el conjunto de casos de prueba en el directorio/t
.
prove -Itest-nginx/lib -r t
Después de la instalación, veamos el caso de prueba más simple en test::nginx
. El siguiente código está adaptado de la documentación oficial, y he eliminado todos los parámetros de control personalizados.
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
Aunque test::nginx
está escrito en Perl y funciona como uno de sus módulos, ¿puedes ver algo en Perl o en cualquier otro lenguaje en la prueba anterior? Así es. Esto se debe a que test::nginx
es la propia implementación de DSL del autor en Perl, abstraída específicamente para probar NGINX y OpenResty.
Por lo tanto, cuando vemos este tipo de prueba por primera vez, es muy probable que no la entendamos. Pero no te preocupes; analicemos el caso de prueba anterior.
En primer lugar, use Test::Nginx::Socket;
, que es la forma en que Perl hace referencia a las bibliotecas, al igual que require
en Lua. Esto también nos recuerda que test::nginx
es un programa en Perl.
La segunda línea, run_tests();
, es una función en Perl de test::nginx
, la función de entrada para el marco de pruebas. Si deseas llamar a cualquier otra función en Perl de test::nginx
, deben colocarse antes de run_tests
para que sean válidas.
El __DATA__
en la tercera línea es una bandera que indica que todo lo que está debajo son datos de prueba, y las funciones de Perl deben completarse antes de esta bandera.
El siguiente === TEST 1: set Server
, el título del caso de prueba, indica el propósito de esta prueba, y tiene una herramienta que automáticamente asigna la numeración interna.
--- config
es el campo de configuración de NGINX. En el caso anterior, usamos comandos de NGINX, no Lua, y si deseas agregar código Lua, lo harás aquí con una directiva como content_by_lua
.
--- request
se usa para simular un terminal que envía una solicitud, seguido de GET /foo
, que especifica el método y el URI de la solicitud.
--- response_headers
, que se usa para detectar los encabezados de respuesta. El siguiente Server: Foo
indica el header
y value
que deben aparecer en los encabezados de respuesta. Si no es así, la prueba fallará.
El último, --- response_body
, se usa para detectar el cuerpo de la respuesta. El siguiente hi
es la cadena que debe aparecer en el cuerpo de la respuesta; si no lo hace, la prueba fallará.
Bueno, aquí termina el análisis del caso de prueba más simple. Entonces, entender el caso de prueba es un requisito previo para completar el trabajo de desarrollo relacionado con OpenResty.
Escribe tus casos de prueba
A continuación, es hora de entrar en las pruebas prácticas. ¿Recuerdas cómo probamos el servidor Memcached en el último artículo? Así es; usamos resty
para enviar la solicitud manualmente, lo cual está representado por el siguiente código.
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)'
Pero, ¿no es lo suficientemente inteligente enviarlo manualmente? No te preocupes. Podemos intentar convertir las pruebas manuales en automatizadas después de aprender test::nginx
. Por ejemplo:
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]
En este caso de prueba, he agregado --- stream_config
, --- stream_server_config
, --- no_error_log
como elementos de configuración, pero son esencialmente lo mismo, es decir.
Los datos y las pruebas se simplifican para mejorar la legibilidad y la extensibilidad al abstraer la configuración.
Aquí es donde test::nginx
es fundamentalmente diferente de otros marcos de pruebas. Este DSL es una espada de doble filo, ya que hace que la lógica de las pruebas sea clara y fácilmente extensible. Sin embargo, aumenta el costo de aprendizaje, requiriendo que aprendas una nueva sintaxis y configuración antes de poder comenzar a escribir casos de prueba.
Resumen
El test::nginx
es poderoso, pero muchas veces puede no ser adecuado para tu escenario. ¿Por qué usar un cañón para matar una mosca? En OpenResty, también tienes la opción de usar el marco de pruebas basado en aserciones busted
. El busted
combinado con resty
se convierte en una herramienta de línea de comandos y también puede satisfacer muchas necesidades de pruebas.
Finalmente, te dejo una pregunta. ¿Puedes ejecutar esta prueba para Memcached
localmente? Si puedes agregar un nuevo caso de prueba, sería genial.