Obstáculo na Contribuição de Código: `test::nginx`
API7.ai
November 17, 2022
Testar é uma parte essencial do desenvolvimento de software. O conceito de Desenvolvimento Orientado a Testes (TDD, do inglês Test Driven Development) tornou-se tão popular que quase todas as empresas de software possuem uma equipe de QA (Quality Assurance) para cuidar do trabalho de testes.
Testes são a base da qualidade e da grande reputação do OpenResty, mas também são a parte mais negligenciada dos projetos de código aberto do OpenResty. Muitos desenvolvedores usam o lua-nginx-module
diariamente e ocasionalmente executam um gráfico de chamas (flame graph), mas quantas pessoas executam os casos de teste? Até mesmo muitos projetos de código aberto baseados no OpenResty não possuem casos de teste. Mas um projeto de código aberto sem casos de teste e integração contínua não é confiável.
No entanto, ao contrário das empresas comerciais, na maioria dos projetos de código aberto não há engenheiros de testes de software dedicados. Então, como eles garantem a qualidade do código? A resposta é simples: "automação de testes" e "integração contínua", com os pontos-chave sendo automação e continuidade, ambos os quais o OpenResty alcançou ao máximo.
O OpenResty possui 70 projetos de código aberto, e seus testes unitários, testes de integração, testes de desempenho, testes de simulação (mock testing), testes de fuzzing (fuzz testing) e outras cargas de trabalho são desafiadores de resolver manualmente apenas pelos contribuidores da comunidade. Portanto, o OpenResty investiu mais em testes automatizados desde o início. Isso pode parecer desacelerar o projeto a curto prazo, mas pode-se dizer que o investimento nessa área é muito rentável a longo prazo. Então, quando converso com outros engenheiros sobre a lógica e o conjunto de ferramentas de testes do OpenResty, eles ficam impressionados.
Vamos falar sobre a filosofia de testes do OpenResty.
Conceito
test::nginx
é o núcleo da arquitetura de testes do OpenResty, que é usado pelo próprio OpenResty e pelas bibliotecas lua-resty
ao redor para organizar e escrever conjuntos de testes. É uma estrutura de testes com um limiar muito alto. A razão é que, ao contrário das estruturas de testes comuns, o test::nginx
não é baseado em asserções e não usa a linguagem Lua, o que exige que os desenvolvedores aprendam e usem o test::nginx
do zero e revertam seu conhecimento inerente sobre estruturas de testes.
Conheço vários contribuidores do OpenResty que podem enviar código C e Lua para o OpenResty, mas acham difícil escrever casos de teste usando o test::nginx
. Eles ou não sabiam como escrevê-los ou como corrigi-los ao encontrar falhas nos testes. Portanto, chamo o test::nginx
de um obstáculo na contribuição de código.
O test::nginx
combina Perl, dados orientados e DSL (Domain-specific language). Para o mesmo conjunto de casos de teste, ao controlar os parâmetros e variáveis de ambiente, você pode alcançar diferentes efeitos, como execução aleatória, múltiplas repetições, detecção de vazamentos de memória, testes de estresse, etc.
Instalação e exemplos
Antes de usarmos o test::nginx
, vamos aprender como instalá-lo.
Quanto à instalação de software no sistema OpenResty, apenas o método de instalação oficial do CI é o mais oportuno e eficaz; outras formas de instalação sempre encontram vários problemas. É por isso que recomendo que você tome os métodos oficiais como referência, onde você pode encontrar a instalação e o uso do test::nginx
também. Há quatro etapas.
- Primeiro, instale o gerenciador de pacotes do Perl,
cpanminus
. - Em seguida, instale o
test::nginx
viacpanm
.
sudo cpanm --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1)
- Depois, clone o código-fonte mais recente.
git clone https://github.com/openresty/test-nginx.git
- Por fim, carregue a biblioteca
test-nginx
via o comandoprove
do Perl e execute o conjunto de casos de teste no diretório/t
.
prove -Itest-nginx/lib -r t
Após a instalação, vamos ver o caso de teste mais simples no test::nginx
. O código a seguir foi adaptado da documentação oficial, e removi todos os parâmetros de controle 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
Embora o test::nginx
seja escrito em Perl e funcione como um dos módulos, você consegue ver algo em Perl ou em qualquer outra linguagem no teste acima? Isso mesmo. É porque o test::nginx
é a própria implementação de DSL do autor em Perl, abstraída especificamente para testar o NGINX e o OpenResty.
Então, quando vemos esse tipo de teste pela primeira vez, provavelmente não entendemos. Mas não se preocupe; vamos analisar o caso de teste acima.
Primeiro, use Test::Nginx::Socket;
, que é a forma como o Perl referencia bibliotecas, assim como o require
no Lua. Isso também nos lembra que o test::nginx
é um programa Perl.
A segunda linha, run_tests();
, é uma função Perl no test::nginx
, a função de entrada da estrutura de testes. Se você quiser chamar qualquer outra função Perl no test::nginx
, elas devem ser colocadas antes de run_tests
para serem válidas.
O __DATA__
na terceira linha é um marcador que indica que tudo abaixo dele são dados de teste, e as funções Perl devem ser concluídas antes desse marcador.
O próximo === TEST 1: set Server
, o título do caso de teste, indica o propósito deste teste, e ele possui uma ferramenta que automaticamente organiza a numeração internamente.
--- config
é o campo de configuração do NGINX. No caso acima, usamos comandos do NGINX, não Lua, e se você quiser adicionar código Lua, fará isso aqui com uma diretiva como content_by_lua
.
--- request
é usado para simular um terminal enviando uma solicitação, seguido por GET /foo
, que especifica o método e o URI da solicitação.
--- response_headers
, que é usado para detectar cabeçalhos de resposta. O seguinte Server: Foo
indica o header
e o value
que devem aparecer nos cabeçalhos de resposta. Caso contrário, o teste falhará.
O último, --- response_body
, é usado para detectar o corpo da resposta. O seguinte hi
é a string que deve aparecer no corpo da resposta; caso contrário, o teste falhará.
Bem, aqui, o caso de teste mais simples foi analisado. Então, entender o caso de teste é um pré-requisito para concluir o trabalho de desenvolvimento relacionado ao OpenResty.
Escreva seus casos de teste
Agora, é hora de colocar a mão na massa. Lembra como testamos o servidor Memcached no último artigo? Isso mesmo; usamos o resty
para enviar a solicitação manualmente, o que é representado pelo seguinte 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)'
Mas enviar manualmente não é muito inteligente, certo? Não se preocupe. Podemos tentar transformar testes manuais em automatizados após aprender o test::nginx
. Por exemplo:
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]
Neste caso de teste, adicionei --- stream_config
, --- stream_server_config
, --- no_error_log
como itens de configuração, mas eles são essencialmente os mesmos, ou seja,
Os dados e testes dos testes são simplificados para melhorar a legibilidade e a extensibilidade, abstraindo a configuração.
É aqui que o test::nginx
é fundamentalmente diferente de outras estruturas de testes. Essa DSL é uma faca de dois gumes, pois torna a lógica de teste clara e facilmente extensível. No entanto, aumenta o custo de aprendizado, exigindo que você reaprenda uma nova sintaxe e configuração antes de começar a escrever casos de teste.
Resumo
O test::nginx
é poderoso, mas muitas vezes pode não ser adequado para o seu cenário. Por que usar um canhão para matar uma mosca? No OpenResty, você também tem a opção de usar a estrutura de testes baseada em asserções busted
. O busted
combinado com o resty
se torna uma ferramenta de linha de comando e também pode atender a muitas necessidades de testes.
Por fim, deixo uma pergunta para você. Você consegue executar esse teste para o Memcached
localmente? Se você puder adicionar um novo caso de teste, seria ótimo.