Abstrair AWS SQS no Ruby Parte 2: Criando testes automatizados
Este post é a segunda parte do post Abstrair AWS SQS no Ruby. Nele mostro como criar testes automatizados para a classe que criamos (QueueManager)
No post anterior escrevemos uma classe chamada QueueManager, que é uma abstração do SDK da AWS para o SQS. Agora vamos ver como criar testes automatizados para a classe que criamos.
A primeira coisa que devemos fazer é alterar o arquivo spec_helper.rb para que consigamos criar os stubs para o AWS SQS. Além disso, devemos fazer o require tanto do aws-sdk quanto do queue_manager.rb para que os nossos testes automatizados funcionem.
Lembrando que estamos utilizando o Ruby 2.2.7 e aws-sdk 2.9.11.
Preparando o ambiente dos testes automatizados
Dentro do diretório do nosso projeto, abra ./spec/spec_helper.rb no seu editor de texto preferido e deixe o conteúdo da seguinte forma:
require 'aws-sdk'
require 'queue_manager.rb'
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
expectations.syntax = [:should, :expect]
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
Aws.config.update(stub_responses: true)
end
Como foi dito logo no segundo parágrafo deste post, fizemos os requires necessários, após temos algumas configurações básicas do rspec, e na penúltima linha habilitamos o stub para o AWS SDK v2.
Feito isso, vamos para o próximo passo.
Criando o esqueleto do arquivo dos testes automatizados
Vamos começar criando o arquivo que conterá os testes. Para isso, dentro do diretório do projeto crie um arquivo com o nome de queue_manager_spec.rb, dentro do diretório spec:
touch spec/queue_manager_spec.rb
Abra o arquivo e ponha os seguintes comandos dentro dele e depois salve:
require 'spec_helper.rb'
describe "QueueManager" do
let(:queue_url){"https://sqs.us-west-2.amazonaws.com/943154236803/gabrielzuqueto_eti_br"}
let(:queue_name){:gabrielzuqueto_eti_br}
let(:client_aws_sqs){Aws::SQS::Client.new}
before do
allow_any_instance_of(QueueManager).to receive(:client).and_return client_aws_sqs
end
end
Bem simples! Adicionamos o arquivo responsável pelas configurações globais dos nossos testes e depois iniciamos um bloco com a descrição do arquivo de teste. Na descrição botamos QueueManager para que fique claro que os testes sãopara esta classe.
Dentro criamos três métodos para nos auxiliar nos testes automatizados. O primeiro retorna a URL da fila, o segundo retorna o nome da fila e o terceiro retorna uma instância do client da AWS para SQS. Iremos utilizá-los nos stubs.
Por último dissemos para o teste que todas vez que o método client da classe QueueManager for chamado, ele irá retornar o client que iremos utilizar para configurar os stubs do SQS. Fizemos nosso primeiro stub!
Vamos aos testes!
Primeiro e segundo teste automatizado
Vamos testar a criação de uma instância da nossa classe e vamos verificar se os parâmetros que enviamos foram guardados nesta nova instância. Para isso faremos 2 testes, um com o valor default do parâmetro visibility_timeout e outro setando um novo valor para este argumento.
describe "When try instance object" do
it "should return a new instance of QueueManager visibility_timeout=60" do
queue_manager = QueueManager.new(queue_name)
expect( queue_manager.instance_eval{ queue_name } ).to eq(queue_name)
expect( queue_manager.instance_eval{ visibility_timeout } ).to eq(60)
end
it "with visibility_timeout arg equals 120 should return a new instance of QueueManager visibility_timeout=120" do
queue_manager = QueueManager.new(queue_name, 120)
expect( queue_manager.instance_eval{ queue_name } ).to eq(queue_name)
expect( queue_manager.instance_eval{ visibility_timeout } ).to eq(120)
end
end
Ponha este código antes do último end do arquivo, salve e vamos executar o teste para ver se está tudo Ok.
bundle exec rspec
Para a nossa surpresa, o primeiro teste quebrou!
F.
Failures:
1) QueueManager When try instance object should return a new instance of QueueManager visibility_timeout=60
Failure/Error:
def initialize(queue_name, visibility_timeout = 1.minutes)
@queue_name = queue_name
@visibility_timeout = visibility_timeout
end
NoMethodError:
undefined method `minutes' for 1:Fixnum
# ./lib/queue_manager.rb:2:in `initialize'
# ./spec/queue_manager_spec.rb:13:in `new'
# ./spec/queue_manager_spec.rb:13:in `block (3 levels) in <top (required)>'
Finished in 0.14077 seconds (files took 0.14085 seconds to load)
2 examples, 1 failure
Failed examples:
rspec ./spec/queue_manager_spec.rb:12 # QueueManager When try instance object should return a new instance of QueueManager visibility_timeout=60
Isso aconteceu pois utilizamos o método minutes no valor default do visibility_timeout, no método initialize da class QueueManager. O problema é que o Rails é quem provê este método e nós estamos Ruby puro.
Reparou o quanto é importante ter testes automatizados? Se não fosse por ele, este código provavelmente iria para produção e quebraria a aplicação.
Vamos corrigir a classe QueueManager!
Abra lib/queue_manager.rb em seu editor de texto e na linha 2 altere visibility_timeout = 1.minutes por visibility_timeout = 60.
class QueueManager
def initialize(queue_name, visibility_timeout = 60)
@queue_name = queue_name
@visibility_timeout = visibility_timeout
end
...
Pronto! Salve o arquivo e execute os testes novamente.
..
Finished in 0.15856 seconds (files took 0.1605 seconds to load)
2 examples, 0 failures
Corrigimos o bug! :D
Terceiro teste automatizado
Vamos testar o método client da classe QueueManager. Este método deve retornar uma instância da classe Aws::SQS::Client.
describe "When call client method" do
it "should return a instance of Aws::SQS::Client" do
queue_manager = QueueManager.new(queue_name)
expect( queue_manager.instance_eval{ client } ).to be_instance_of(Aws::SQS::Client)
end
end
Adicione estas linhas antes do último end do nosso arquivo de teste, salve o arquivo e execute os testes.
...
Finished in 0.13722 seconds (files took 0.14355 seconds to load)
3 examples, 0 failures
Tudo certo, vamos para o quarto teste.
Quarto teste automatizado
Vamos agora criar o teste para o método privador create_queue da classe QueueManager. Adicione estas linhas antes do último end do nosso arquivo de testes automatizados:
describe "When call create_queue method" do
it "should return the queue url" do
client_aws_sqs.stub_responses(:create_queue, queue_url: queue_url)
queue_manager = QueueManager.new(queue_name)
expect( queue_manager.instance_eval{ create_queue } ).to eq(queue_url)
end
end
Básicamente estamos criando um stub para o método create_queue da classe Aws::SQS::Client. Logo, se tudo estiver certo, ao chamar o create_queue da classe QueueManager, será retornada a URL da fila como esperamos. Salve o arquivo e execute os testes.
Mais uma vez, quebrou! O motivo é o mesmo: Utilizamos métodos providos pelo Rails em um projeto de Ruby puro.
...F
Failures:
1) QueueManager When create queue should return the queue url
Failure/Error: client.create_queue({queue_name: queue_name},{:http_open_timeout => 1.hours, :http_read_timeout => 1.hours, :logger => nil, :visibility_timeout => 1.minutes}).queue_url
NoMethodError:
undefined method `hours' for 1:Fixnum
# ./lib/queue_manager.rb:90:in `create_queue'
# ./spec/queue_manager_spec.rb:36:in `block (4 levels) in <top (required)>'
# ./spec/queue_manager_spec.rb:36:in `instance_eval'
# ./spec/queue_manager_spec.rb:36:in `block (3 levels) in <top (required)>'
Finished in 0.18209 seconds (files took 0.16784 seconds to load)
3 examples, 1 failure
Failed examples:
rspec ./spec/queue_manager_spec.rb:33 # QueueManager When create queue should return the queue url
Vamos corrigir!
Abra lib/queue_manager.rb em seu editor de texto e na linha 90 altere:
client.create_queue({queue_name: queue_name},{:http_open_timeout => 1.hours, :http_read_timeout => 1.hours, :logger => nil, :visibility_timeout => 1.minutes}).queue_url
Por:
client.create_queue({queue_name: queue_name},{:http_open_timeout => 3600, :http_read_timeout => 3600, :logger => nil, :visibility_timeout => 60}).queue_url
Salve o arquivo e rode os testes novamente.
....
Finished in 0.22548 seconds (files took 0.14047 seconds to load)
4 examples, 0 failures
Mais um bug corrigido! Provavelmente teremos mais.
Quinto e sexto teste automatizado
Agora vamos criar os testes para o método get_queue_url da classe QueueManager. Precisamos de 2 testes, pois ele pergunta ao SQS a URL de fila e caso amesma não exista, ele a cria. logo, são 2 comportamentos que devemos testar.
describe "When call get_queue_url method" do
it "should return the queue url" do
client_aws_sqs.stub_responses(:get_queue_url, queue_url: queue_url)
queue_manager = QueueManager.new(queue_name)
expect( queue_manager.instance_eval{ get_queue_url } ).to eq(queue_url)
end
it "if queue not exists should create queue and return your URL" do
client_aws_sqs.stub_responses(:get_queue_url, 'NonExistentQueue')
client_aws_sqs.stub_responses(:create_queue, queue_url: queue_url)
queue_manager = QueueManager.new(queue_name)
expect( queue_manager.instance_eval{ get_queue_url } ).to eq(queue_url)
end
end
No primeiro teste é como se a fila já existisse, logo ele retorna a URL de cara.
No segundo a gente criar o stub de erro de fila não existente para o método get_queue_url do Aws::SQS::Client e após a gente cria o stub de fila criada para o método create_queue da mesma classe dita anteriormente. Após a gente testa se a URL da fila foi retornada, assim garantindo que o método get_queue_url da classe QueueManager está funcionando!
Execute novamente os testes.
......
Finished in 0.23801 seconds (files took 0.14207 seconds to load)
6 examples, 0 failures
Tudo correto, vamos para o próximo!
Sétimo teste automatizado
Agora iremos testar o método queue_url da classe QueueManager.
describe "When call queue_url method" do
it "should return the queue url" do
client_aws_sqs.stub_responses(:get_queue_url, queue_url: queue_url)
queue_manager = QueueManager.new(queue_name)
expect( queue_manager.instance_eval{ queue_url } ).to eq(queue_url)
end
end
Mais uma vez adicione estas linhas antes do último end do nosso arquivo de teste, salve e execute os testes.
.......
Finished in 0.24331 seconds (files took 0.13894 seconds to load)
7 examples, 0 failures
Tudo certo! Próximo!
Agora que todos os métodos privados foram testados e o método público initialize também, iremos começar a testar os outros métodos públicos. Sim, todos os métodos privados foram testados, pois visibility_timeout e queue_name da classe QueueManager foram testados logo no primeiro teste.
Oitavo teste automatizado
Vamos testar o método send_message da classe QueueManager. Vamos verificar apenas se não houve erro ao chamar o método.
describe "When call send_message method" do
it "should not return error" do
client_aws_sqs.stub_responses(:get_queue_url, queue_url: queue_url)
queue_manager = QueueManager.new(queue_name)
expect { queue_manager.send_message("Something") }.to_not raise_error
end
end
Adicione estas linhas no arquivo de teste como fez com os outros, salve e execute os testes.
.......F
Failures:
1) QueueManager When call send_message method should not return error
Failure/Error: expect { queue_manager.send_message("Something") }.to_not raise_error
expected no Exception, got #<NameError: undefined local variable or method `data' for #<QueueManager:0x007f7d0c8bac00>> with backtrace:
# ./lib/queue_manager.rb:8:in `send_message'
# ./spec/queue_manager_spec.rb:67:in `block (4 levels) in <top (required)>'
# ./spec/queue_manager_spec.rb:67:in `block (3 levels) in <top (required)>'
# ./spec/queue_manager_spec.rb:67:in `block (3 levels) in <top (required)>'
Finished in 0.26467 seconds (files took 0.14186 seconds to load)
8 examples, 1 failure
Failed examples:
rspec ./spec/queue_manager_spec.rb:64 # QueueManager When call send_message method should not return error
Mais um bug! Em lib/queue_manager.rb:8, estamos tentando enviar o conteúdo da variável data, porém esta variável não existe. Altere para message_data, salve o arquivo.
class QueueManager
def initialize(queue_name, visibility_timeout = 60)
@queue_name = queue_name
@visibility_timeout = visibility_timeout
end
def send_message(message_data)
client.send_message({queue_url: queue_url, message_body: message_data })
end
...
Execute os testes e vamos em frente!
........
Finished in 0.24677 seconds (files took 0.14044 seconds to load)
8 examples, 0 failures
Nono e décimo teste automatizado
Vamos criar dois testes para o método receive_message da classe QueueManager: um para o caso de não existir mensagens na fila e o outro caso exista.
describe "When call receive_message method" do
before do
client_aws_sqs.stub_responses(:get_queue_url, queue_url: queue_url)
end
it "and there is no message should return response with messages array empty" do
queue_manager = QueueManager.new(queue_name)
expect( queue_manager.receive_message.messages.count ).to eq(0)
end
it "should return not empty message array" do
client_aws_sqs.stub_responses(:receive_message, messages: [
message_id: "Something",
receipt_handle: "Something",
body: {Something: "Something"}.to_json
])
expected_message = [Aws::SQS::Types::Message.new(message_id: "Something", receipt_handle: "Something", body: {"Something"=>"Something"}.to_json, attributes: {}, message_attributes: {})]
queue_manager = QueueManager.new(queue_name)
expect(queue_manager.receive_message.messages).to eq(expected_message)
end
end
Adicione ao seu arquivo de teste, salve e execute os testes automatizados.
..........
Finished in 0.26132 seconds (files took 0.14142 seconds to load)
10 examples, 0 failures
Décimo primeiro teste automatizado
Vamos testar o método delete_message da classe QueueManager. Como no método send_message, iremos apenas testar se não ocorreu erro ao chamar o método do delete_message da classe Aws::SQS::Client.
describe "When call delete_message method" do
it "should not return error" do
client_aws_sqs.stub_responses(:get_queue_url, queue_url: queue_url)
queue_manager = QueueManager.new(queue_name)
expect { queue_manager.delete_message("Something") }.to_not raise_error
end
end
Já sabemos o que fazer a cada teste novo…
...........
Finished in 0.26799 seconds (files took 0.1416 seconds to load)
11 examples, 0 failures
Tudo Ok! Próximo! :D
Décimo segundo teste automatizado
Vamos agora testar o método send_message_batch da classe QueueManager. O teste será parecido com os outros 2 anteriores.
describe "When call send_message_batch method" do
it "should not return error" do
client_aws_sqs.stub_responses(:get_queue_url, queue_url: queue_url)
queue_manager = QueueManager.new(queue_name)
expect { queue_manager.send_message_batch([{id: "1", message_body: "Something"},{id: "2", message_body: "Anything"}]) }.to_not raise_error
end
end
Note que é obrigatório enviar um ID no JSON da mensagem. Veja mais detalhes na documentação oficial do SDK AWS v2.
Executando todos os testes temos:
............
Finished in 0.50623 seconds (files took 0.21058 seconds to load)
12 examples, 0 failures
Perfeito!
Décimo terceiro teste automatizado
Agora vamos criar o teste do método receive_message_batch da classe QueueManager.
describe "When call receive_message_batch method" do
it "should return messages array" do
client_aws_sqs.stub_responses(:get_queue_url, queue_url: queue_url)
client_aws_sqs.stub_responses(:receive_message, messages: [
{message_id: "Something",
receipt_handle: "Something",
body: {Something: "Something"}.to_json},
{message_id: "Something_2",
receipt_handle: "Something_2",
body: {Something: "Something_2"}.to_json}
])
expected_message = [
Aws::SQS::Types::Message.new(message_id: "Something", receipt_handle: "Something", body: {"Something"=>"Something"}.to_json, attributes: {}, message_attributes: {}),
Aws::SQS::Types::Message.new(message_id: "Something_2", receipt_handle: "Something_2", body: {"Something"=>"Something_2"}.to_json, attributes: {}, message_attributes: {})
]
queue_manager = QueueManager.new(queue_name)
expect(queue_manager.receive_message_batch.messages).to eq(expected_message)
end
end
Ao executar os testes, teremos o seguinte resultado:
.............
Finished in 0.56844 seconds (files took 0.20844 seconds to load)
13 examples, 0 failures
Décimo quarto teste automatizado
Agora vamos testar a exclusão por batch. Para isso, vamos criar um teste para o método delete_message_batch da classe QueueManager.
describe "When call delete_message_batch method" do
it "should return messages array" do
client_aws_sqs.stub_responses(:get_queue_url, queue_url: queue_url)
queue_manager = QueueManager.new(queue_name)
expect { queue_manager.delete_message_batch([{id: "Something", receipt_handle: "Something"}]) }.to_not raise_error
end
end
Mais uma vez, vamos rodar novamente os teste.
..............
Finished in 0.51683 seconds (files took 0.20637 seconds to load)
14 examples, 0 failures
Décimo quinto teste automatizado
Vamos criar o teste para o método purge_queue da classe QueueManager.
describe "When call purge_queue method" do
it "should return messages array" do
client_aws_sqs.stub_responses(:get_queue_url, queue_url: queue_url)
queue_manager = QueueManager.new(queue_name)
expect { queue_manager.purge_queue }.to_not raise_error
end
end
Ao executar novamente o arquivo de testes automatizados, temos:
...............
Finished in 0.51229 seconds (files took 0.23809 seconds to load)
15 examples, 0 failures
Décimo sexto teste automatizado
Estamos quase terminando a criação dos testes automatizados. Vamos criar o teste para o método delete_queue da classe QueueManager.
describe "When call delete_queue method" do
it "should return messages array" do
client_aws_sqs.stub_responses(:get_queue_url, queue_url: queue_url)
queue_manager = QueueManager.new(queue_name)
expect { queue_manager.delete_queue }.to_not raise_error
expect( queue_manager.instance_eval{ @queue_url } ).to eq(nil)
end
end
Neste teste verificamos também a variável @queue_url, pois sabemos que ao excluir a fila, a gente seta esta variável com nulo.
................
Finished in 0.54293 seconds (files took 0.19049 seconds to load)
16 examples, 0 failures
Décimo sétimo, Décimo oitavo, Décimo novo e Vigésimo teste automatizado
Vamos criar os testes que retornam a quantidade de mensagens na fila.
describe "When call " do
before do
client_aws_sqs.stub_responses(:get_queue_url, queue_url: queue_url)
client_aws_sqs.stub_responses(:get_queue_attributes, attributes: {
"ApproximateNumberOfMessages" => "1",
"ApproximateNumberOfMessagesNotVisible" => "1",
"ApproximateNumberOfMessagesDelayed" => "1"
})
end
it "queue_size method should return queue size" do
queue_manager = QueueManager.new(queue_name)
expect(queue_manager.queue_size).to eq(3)
end
it "queue_available_size method should return queue available size" do
queue_manager = QueueManager.new(queue_name)
expect(queue_manager.queue_available_size).to eq(1)
end
it "queue_unavailable_size method should return queue unavailable size" do
queue_manager = QueueManager.new(queue_name)
expect(queue_manager.queue_unavailable_size).to eq(1)
end
it "queue_waiting_size method should return queue waiting size" do
queue_manager = QueueManager.new(queue_name)
expect(queue_manager.queue_waiting_size).to eq(1)
end
end
A diferença neste teste é que criamos o stub para o método get_queue_attributes da classe Aws::SQS::Client.
Vamos executar os testes :)
....................
Finished in 0.58918 seconds (files took 0.20795 seconds to load)
20 examples, 0 failures
Tudo Ok. Agora vamos para o último teste da nossa classe.
Vigésimo primeiro e último teste automatizado
Este teste é bem simples. Verificamos apenas se o método poller da classe QueueManager retorna uma instância da classe Aws::SQS::QueuePoller.
describe "When call poller method" do
it "should return a instance of Aws::SQS::QueuePoller" do
queue_manager = QueueManager.new(queue_name)
expect( queue_manager.poller ).to be_instance_of(Aws::SQS::QueuePoller)
end
end
Rodando o teste, temos:
.....................
Finished in 0.53894 seconds (files took 0.2233 seconds to load)
21 examples, 0 failures
Chegamos ao final deste post! Conseguimos cobrir 100% da nossa classe QueueManager por testes automatizados.
Pudemos ver na prática, como testes automatizados são importantes em um projeto. Encontramos bugs apenas rodando os testes.
Baixe o código-fonte: https://github.com/gabrielzuqueto/queue_manager_aws_sdk_v2_sqs_ruby
Confira aqui mais detalhes sobre como utilizar o Aws::SQS::QueuePoller
Livros indicados
É vital que um profissional de T.I conheça boas práticas e saiba aplicá-las independente da linguagem ou ferramenta.
Pensando nisso, separei alguns títulos que fazem parte da minha bilioteca pessoal.
Aproveite e invista na sua educação, pois é a base de tudo para uma carreira incrível.
Deixe seu comentário
Atenção: Os comentários abaixo são de inteira responsabilidade de seus respectivos autores e não representam, necessariamente, a opinião do autor desse blog.
Não perca mais nenhum post!
Cadastre-se e receba novos posts diretamente em seu e-mail.
Escolhidos para você