Teste de Carga com The Grinder

Transcrição

Teste de Carga com The Grinder
Teste de Carga com The
Grinder
Uma alternativa viável ao JMeter
Filipe Tagliatti
[email protected]
Graduado em Sistemas de Informação, com quase 4 anos de experiencia em
desenvolvimento de sistemas web em PHP e Java, atualmente dedicasse no
desenvolvimento em soluções completas para organizações do Terceiro Setor como
OMGs e fundações.
Cesar Roberto de S. Loureiro Júnior
Graduando em Sistema de Informação pela Faculdade Metodista Granbery.
Atualmente Analista de TI na empresa Virtual Agência Digital.
De que se trata o artigo
O artigo mostra uma alternativa ao JMeter para teste de carga, o The Grinder.
Mostrando como instalar, configurar e como ele pode ajudar no dia a dia do tester ou
desenvolvedor, com funcionalidades que lhe dão uma maior liberdade na criação dos
seus testes.
Em que situação o tema é útil
O artigo é útil para aqueles que querem começar a aplicar a técnica de teste de carga em
seus sistemas ou para aqueles que já conhecem sobre o assunto, mas pode através desse
artigo conhecer uma nova ferramenta para auxilia-lo nessa tarefa.
TESTE DE CARGA COM THE GRINDER
O artigo começa introduzindo o leitor sobre alguns conceitos de teste de software e
teste de carga. Em sequencia é mostrado como baixar e instalar o The Grinder e fazer
um teste de carga de exemplo. Logo após apresentados um pouco da estrutura do
software e as estatísticas que ele gera. Também alguns scripts prontos que você pode
aproveitar no dia a dia e por fim apresentamos um estudo de caso onde fazendo um teste
de carga e analisamos seus resultados.
Introdução
Nesse artigo vamos abordar a configuração e a forma de utilizar a ferramenta
para testes de carga em sites chamada Grinder, porém antes de começar a falar sobre o
software é preciso intender um pouco sobre a importância de fazer testes, o que vem a
ser uma ferramenta de teste de carga, e quais são as diferenças entre os testes.
O teste em software é muito importante hoje em dia, porém não é uma novidade
pois desde o início do desenvolvimento de sistemas o teste e usado para garantir pelo
menos o funcionamento básico do que foi contratado para fazer o sistema, muitas
empresas utilizam a desculpa de que
“[...] testar o software, é que teste custa caro, mas Pressman já apresentou em
seu Livro de Engenharia de Software que o custo do defeito é progressivo, ou
seja, encontrar o defeito na fase de engenharia de requisitos custa 1 enquanto
encontrar o defeito durante a fase de uso custa 100 vezes mais, então utilizar o
teste, reduz custo e não aumenta”. ROCHA Fabio Gomes.
Sendo que os gastos com manutenções para correção de defeitos pode ter um
custo muito elevado para a empresa causando gastos desastrosos para a manutenção,
para evitar possíveis problemas com clientes por ser desenvolvidos sistemas com muitas
falhas e alto custo na manutenção, foi buscado métodos para garantir a qualidade de
softwares. Bastos et. al. abordam alguns pontos dentro da qualidade de software que
precisam ser consideradas: a confiança que é quando o sistema resiste a falhas durante a
execução, a funcionalidade quando o sistema comporta o que foi definido por seus
requisitos e a performance que é quando o sistema responde todas as funcionalidade em
tempo hábil. Hoje o teste tem sua área específica em muitas instituições garantindo a
qualidade de todo sistema que e produzidos, e para aproximar os testes do
desenvolvimento LOPES Mateus Bruno Teixeira et. al. apresentam em seu artigo o
modelo “V” (Figura 1) focado no desenvolvimento durante todo o ciclo.
Figura 1. Representação do modelo “V” (GRAIC e JASKIEl, 2002).
E o teste de carga é um dos tipos de testes de softwares onde
“[...] onde o volume de gerado pela ferramenta de geração de carga é
crescente no decorrer do tempo. O objetivo principal é encontrar o limite de
capacidade da aplicação e identificar qual o limitante (codificação, hardware,
tempo de resposta excessivo). Da mesma forma que os outros testes, não é
objetivo do teste de carga encontrar problemas funcionais na aplicação”.
NETO Oscar Nogueira.
com o teste de carga podemos descobrir até mesmo se o hardware que usamos está
sendo desperdiçado ou não funciona de forma correta, se o site possui páginas que
demoram muito para ser carregadas ou se o acesso ao banco demora muito para carregar
as informações, esses fatores podem contribuir para o uso de equipamentos de
balanceamento de carga, e até servidores de contenção.
The Grinder
The Grinder é um framework de testes de carga que facilita a execução de
testes de carga distribuídas entre vários servidores. Os scripts de teste são
escritos em Jython, fornecendo suporte para testar uma ampla gama de
protocolos de rede. The Grinder também vem com um plug-in para testar
serviços HTTP. MAGNANI Mauricio, 2012
Download e Instalação
Esse tópico poderia ser desnecessário se a ferramenta contasse com um método
simples de instalação, mas não é o que acontece, apesar de não ser impossível o
processos pode causar uma certa dor de cabeça em uma primeira vez.
A primeira coisa é fazer o download da ferramenta, que pode ser baixada no site
oficial http://grinder.sourceforge.net/, a versão mais atual até a publicação desse artigo é
a 3. Logo depois de baixar o arquivo .zip.
Depois de descompactar o arquivo no local desejado vamos ao “pulo do gato”
para executa-lo. Primeiro vamos criar um script apenas para teste, para isso crie um
arquivo chamado “teste,py” e coloque o código abaixo no arquivo (Listagem 1):
Listagem 1. Script de Exemplo
from net.grinder.script import Test
from net.grinder.script.Grinder import grinder
from net.grinder.plugin.http import HTTPRequest
test1 = Test(1, "Request resource")
request1 = HTTPRequest()
test1.record(request1)
class TestRunner:
def __call__(self):
result = request1.GET("http://www.google.com.br")
Depois disso abra o arquivo grinder.properties e procure pelo parâmetro
grinder.script, substitua o valor pelo nome do arquivo que criamos para teste, que no
nosso caso se chama teste.py.
Agora vamos a execução da ferramenta para isso é recomendado cria um
arquivo bat se for Windows ou sh se for Linux. Para esse exemplo utilizamos a
plataforma Windows.
Figura 2
A Figura 2 mostra o primeiro script que podemos chamar de console por se
tratar do console da ferramenta. Logo após dever executar o próximo script.
Figura 3
A Figura 3 mostra o segundo script que deve ser executado que podemos
chamar de agente que tem o papel de ler as configurações executar os teste, já o console
apenas nos mostra o resultado de forma gráfica.
Uma observação importante, os scripts devem ser executados na ordem citada,
primeiro o console depois o agente.
Quando o script do agente for executado ou se foi executado, mas ainda está
processando o botão de start fica bloqueado (Figura 4), e quando o agente está
devidamente iniciado e pronto para executar, esse botão é desbloqueado (Figura 5).
Figura 4
Figura 5
Depois que estiver tudo ok é só clicar no botão start e que os testes irão começar
(Figura 6).
Figura 6.
Estrutura
Antes de começar a trabalhar com o Grinder é importante saber como ele é
estruturado, para assim podermos como utilizar a ferramenta corretamente e extrair ao
máximo dela.
 Agentes (Agents) - Você geralmente têm um único agente em cada máquina
injetora de carga, que irá iniciar um número configurado de processos de
trabalho. Se os agentes podem se conectar ao console, eles vão esperar por um
sinal para começar antes de passar fora de um local de grinder.properties arquivo
para os processos de trabalho.
 Trabalhadores (Workers) - Trabalhadores, como você pode imaginar, faz o
trabalho de teste. Ou seja, eles realmente executam os scripts de teste de carga.
O arquivo grinder.properties que é passado para o trabalhador pelo agente
define, entre outras coisas, o script que o trabalhador irá executar contra o alvo,
quantos threads o trabalhador vai gerar, e quantas vezes cada um desses tópicos
ira executar o script.
 Console - Esta é uma interface gráfica que pode ser usado para controlar os
agentes, e também exibe as estatísticas recolhidas que são dos trabalhadores.
 TCPProxy - O TCPProxy se interpõe entre o navegador e o servidor de destino,
e pode ser usado para gerar scripts, registrando a atividade do seu navegador,
que podem posteriormente ser executada pelos processos de trabalho. Isto é
realmente útil para a geração de testes de carga que simulam a interação do
usuário com uma aplicação web.
Estatísticas
Nesse ponto vamos abordar as estatísticas que são os geradas nos relatórios
feitos de cada site testado pelo programa exibidos no console e salvos em “*.data”,
onde (*) significa um nome qualquer para o arquivo, como demonstra a Figura 7:
Figura 7. Arquivo de estatísticas geradas pelo sistema
A Figura 7 mostra como são armazenados os resultados que são gerados ao logo
de todo o processo de análise. As estatísticas geradas são demonstradas em uma aba
Graphs contendo as seguintes informações: TPS, media de MS, média de TPS, pico de
TPS, número de testes e erros nos testes. Como é demonstrado na Figura 8.
Figura 8. Gráfico gerado através das estatísticas do sistema
Também e possível observar os resultados no console em forma de tabela
dinâmica contendo as informações da aba Graphs e muitas outras atualizações em
tempo real, como: testes bem sucedidos, tempo médio, tempo médio para o desvio
padrão, comprimento médio de resposta, bytes de resposta por segundo, erros de
resposta, tempo médio para resolver o host, tempo médio para estabelecer conexão,
tempo médio até o primeiro byte. Como demonstra a Figura 9.
Figura 9. Resultados gerados a partir do teste
Vamos falar um pouco sobre o que é cada um desses campos que aparecem no
console:
 Testes bem sucedidos: São teste de HTTP feitos no host determinado
 Tempo médio: É o tempo médio de acesso ao host.
 Comprimento médio de resposta: Tempo médio para resposta do host para
responder aos testes.
 Bytes de resposta por segundo: É o tamanho da resposta em Bytes emitidos
pelo host.
 Erros de resposta: Quantidade de erros que ocorreram ao tentar receber
respostas dos hosts.
 Tempo médio para resolver o host: Tempo para conseguir encontrar a fonte do
host.
 Tempo médio para estabelecer conexão: O tempo para conseguir um conexão
estável para fazer os testes.
 Tempo médio até o primeiro byte: Tempo médio até o primeiro byte de
resposta do host.
Grinder properties
O grinder.properties é o arquivo onde aprendemos e configuramos algumas
propriedades do software Grinder, pode ser configurado por exemplo: o número de
processos, o número de threads de trabalho, o endereço IP ou nome do host o agente
trabalhara, entre outros, como demonstra a figura abaixo:
Figura 2. parte da configuração do arquivo grinder.properties





Número de processos: é a quantidade de processos que pode ser executados ao
mesmo tempo, funciona como os grupos de usuário para quem já utilizou o
JMeter.
Número de threads de trabalho: número de threads que trabalharão ao mesmo
tempo em cada processo, sendo que cada thread corresponde a um usuário
realizando o teste de caraga.
Endereço IP ou nome do host: configurar qual será o host acessado para testes.
Tempo para iniciar o próximo teste: você pode configurar em quanto tempo o
próximo teste será realizado depois que o anterior for realizado.
Duração do teste: você pode programar quanto tempo o teste de carga vai
durar.
Scripts
Antes de iniciar o teste de desempenho, você precisa ter um script de teste.
Scripts de teste no Grinder são escritos na linguagem Jython, que é uma implementação
da linguagem Python para rodar na JVM (Java Virtual Machine). Você pode escrever
seus testes a partir do zero se quiser. Para ajudar nessa tarefa a documentação é bem
escrita e vai ajudar nessa tarefa, outra ajuda é o fato dos desenvolvedores ter
disponibilizados vários exemplos, uma para cada funcionalidade do software.
Vamos ver alguns dos principais scripts que podem ajudar no dia a dia do tester.
O código da Listagem 2 representa o teste em formulários HTTP inclusive com o
upload de arquivos:
Listagem 2. Script para teste em formulários
# HTTP multipart form submission
#
# This script uses the HTTPClient.Codecs class to post itself to the
# server as a multi-part form. Thanks to Marc Gemis.
from
from
from
from
from
net.grinder.script.Grinder import grinder
net.grinder.script import Test
net.grinder.plugin.http import HTTPRequest
HTTPClient import Codecs, NVPair
jarray import zeros
test1 = Test(1, "Upload Image")
request1 = HTTPRequest(url="http://localhost:7001/")
test1.record(request1)
class TestRunner:
def __call__(self):
files = ( NVPair("self", "form.py"), )
parameters = ( NVPair("run number", str(grinder.runNumber)), )
# This is the Jython way of creating an NVPair[] Java array
# with one element.
headers = zeros(1, NVPair)
# Create a multi-part form encoded byte array.
data = Codecs.mpFormDataEncode(parameters, files, headers)
grinder.logger.output("Content type set to %s" %
headers[0].value)
# Call the version of POST that takes a byte array.
result = request1.POST("/upload", data, headers)
Outra funcionalidade muito interessante é fazer teste de envio de e-mail,
podendo então fazer testes em servidores de e-mail, ou mesmo no SMTP da
hospedagem do seu site (Listagem 3).
Listagem 3. Script para teste em servidor de e-mail
#
#
#
#
#
#
#
Email
Send email using Java Mail (http://java.sun.com/products/javamail/)
This Grinder Jython script should only be used for legal email test
traffic generation within a lab testbed environment. Anyone using
this script to generate SPAM or other unwanted email traffic is
violating the law and should be exiled to a very bad place for a
very long time.
from net.grinder.script.Grinder import grinder
from net.grinder.script import Test
from java.lang import System
from javax.mail import Message, Session
from javax.mail.internet import InternetAddress, MimeMessage
emailSendTest1 = Test(1, "Email Send Engine")
class TestRunner:
def __call__(self):
smtpHost = "mailhost"
properties = System.getProperties()
properties["mail.smtp.host"] = smtpHost
session = Session.getInstance(System.getProperties())
session.debug = 1
message = MimeMessage(session)
message.setFrom(InternetAddress("[email protected]
"))
message.addRecipient(Message.RecipientType.TO,
InternetAddress("[email protected]")
)
message.subject = "Test email %s from thread %s" %
(grinder.runNumber,
grinder.th
readNumber)
# One could vary this by pointing to various files for content
message.setText("SMTPTransport Email works from The Grinder!")
transport = session.getTransport("smtp")
# Instrument transport object.
emailSendTest1.record(transport)
transport.connect(smtpHost, "username", "password")
transport.sendMessage(message,
message.getRecipients(Message.RecipientT
ype.TO))
transport.close()
Mais um teste presente no dia a dia do desenvolvedor é o de carga do banco de
dados, com o script da Listagem 4 você consegue fazer inserções e/ou seleções
simultâneas para conferir se seu banco está otimizado ou se o servidor de banco de
dados ira aquentar o “tranco”.
Listagem 4. Script de teste em banco de dados
#
#
#
#
Grinding a database with JDBC
Some simple database playing with JDBC.
To run this, set the Oracle login details appropriately and add the
Oracle thin driver classes to your CLASSPATH.
from
from
from
from
java.sql import DriverManager
net.grinder.script.Grinder import grinder
net.grinder.script import Test
oracle.jdbc import OracleDriver
test1 = Test(1, "Database insert")
test2 = Test(2, "Database query")
# Load the Oracle JDBC driver.
DriverManager.registerDriver(OracleDriver())
def getConnection():
return DriverManager.getConnection(
"jdbc:oracle:thin:@127.0.0.1:1521:mysid", "wls", "wls")
def ensureClosed(object):
try: object.close()
except: pass
# One time initialisation that cleans out old data.
connection = getConnection()
statement = connection.createStatement()
try: statement.execute("drop table grinder_fun")
except: pass
statement.execute("create table grinder_fun(thread number, run
number)")
ensureClosed(statement)
ensureClosed(connection)
class TestRunner:
def __call__(self):
connection = None
insertStatement = None
queryStatement = None
try:
connection = getConnection()
insertStatement = connection.createStatement()
test1.record(insertStatement)
insertStatement.execute("insert into grinder_fun
values(%d, %d)" % (grinder.threadNumber, grinder.runNumber))
test2.record(queryStatement)
queryStatement.execute("select * from grinder_fun where
thread=%d" % grinder.threadNumber)
finally:
ensureClosed(insertStatement)
ensureClosed(queryStatement)
ensureClosed(connection)
O próximo script que apesar de ser um pouco mais complicado de se entender,
tem a função de fazer testes com cookies (Listagem 5).
Listagem 5. Script de teste em banco de dados
# HTTP cookies
# HTTP example which shows how to access HTTP cookies.
#
# The HTTPClient library handles cookie interaction and removes the
# cookie headers from responses. If you want to access these cookies,
# one way is to define your own CookiePolicyHandler. This script
defines
# a CookiePolicyHandler that simply logs all cookies that are sent or
# received.
#
# The script also demonstrates how to query what cookies are cached
for
# the current thread, and how add and remove cookies from the cache.
#
# If you really want direct control over the cookie headers, you
# can disable the automatic cookie handling with:
#
HTTPPluginControl.getConnectionDefaults().useCookies = 0
from net.grinder.script.Grinder import grinder
from net.grinder.script import Test
from net.grinder.plugin.http import HTTPRequest, HTTPPluginControl
from HTTPClient import Cookie, CookieModule, CookiePolicyHandler
from java.util import Date
log = grinder.logger.info
# Set up a cookie handler to log all cookies that are sent and
received.
class MyCookiePolicyHandler(CookiePolicyHandler):
def acceptCookie(self, cookie, request, response):
log("accept cookie: %s" % cookie)
return 1
def sendCookie(self, cookie, request):
log("send cookie: %s" % cookie)
return 1
CookieModule.setCookiePolicyHandler(MyCookiePolicyHandler())
test1 = Test(1, "Request resource")
request1 = HTTPRequest()
test1.record(request1)
class TestRunner:
def __call__(self):
# The cache of cookies for each
at
# the start of each run.
worker thread will be reset
result =
request1.GET("http://localhost:7001/console/?request1")
# If the first response set any cookies for the domain,
# they willl be sent back with this request.
result2 =
request1.GET("http://localhost:7001/console/?request2")
# Now let's add a new cookie.
threadContext = HTTPPluginControl.getThreadHTTPClientContext()
expiryDate = Date()
expiryDate.year += 10
cookie = Cookie("key", "value","localhost", "/", expiryDate, 0)
CookieModule.addCookie(cookie, threadContext)
result =
request1.GET("http://localhost:7001/console/?request3")
# Get all cookies for the current thread and write them to the
log
cookies = CookieModule.listAllCookies(threadContext)
for c in cookies: log("retrieved cookie: %s" % c)
# Remove any cookie that isn't ours.
for c in cookies:
if c != cookie: CookieModule.removeCookie(c, threadContext)
result =
request1.GET("http://localhost:7001/console/?request4")
O intuído desse tópico foi apenas apresentar alguns dos principais scripts que
vem com o Grinder, vários outros podem ser vistos na pasta examples.
Estudo de Caso
Agora apresentamos um exemplo pratico do uso da ferramenta, mais
precisamente analisar as estatísticas e chegar a uma conclusão sobre esse teste de carga.
Para isso criamos uma servlet Java que tem a função de apenas contar o número de
acessos ao site. O código fonte desse servlet pode ser visto na Listagem 6.
Listagem 6. Servlet de contagem de acessos
import
import
import
import
import
import
import
java.io.IOException;
java.io.PrintWriter;
javax.servlet.ServletException;
javax.servlet.annotation.WebServlet;
javax.servlet.http.HttpServlet;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
@WebServlet(value="/RequestCounter")
public class RequestCounter extends HttpServlet{
private static final long serialVersionUID = 1L;
private int requestCount;
public void init() {
requestCount = 0;
}
public void doGet(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, IOException {
response.setContentType("text/html");
requestCount++;
PrintWriter out = response.getWriter();
String title = "Total de Requisições: ";
String docType =
"<!doctype html public \"-//w3c//dtd html 4.0 " +
"transitional//en\">\n";
out.println(docType +
"<html>\n" +
"<head><title>" + title + "</title></head>\n" +
"<body bgcolor=\"#FFFFAA\">\n" +
"<h1 align=\"center\">" + title + "</h1>\n" +
"<h2 align=\"center\">" + requestCount + "</h2>\n" +
"</body></html>");
}
}
O script utilizado foi o mesmo mostrado no tópico de Download e Instalação,
apenas trocamos a url para a do nosso servlet
(http://localhost:8080/TesteCarga/RequestCounter). Para a configuração do arquivo
grynder.properties foi utilizado apenas os parâmetros grinder.script para apontar o
arquivo de script, grinder.processes = 1 que faz com que tenhamos apenas um grupo de
usuários acessando o site, grinder.threads = 1000 que determina o numero de usuários
que vai acessar o site, ou seja, mil usuários e por ultimo grinder.runs = 0 que apenas
determina que vamos usar o Grinder com interface gráfica. O computador utilizado foi
um Intel I7 com 3.10 GB de RAM e o servidor web utilizador foi o Glassfish 3, sendo
que não foi feito nenhum tipo de otimização no servidor e na JVM.
Após alguns minutos de teste obtivemos o seguinte resultado (Figura 10 e 11).
Figura 10. Resultado do Teste de Carga
Figura 11. Resultado do Teste de Carga
Figura 12. Total de requisições ao site
Foram feitos 1.513.838 de testes bem sucedidos e 12.106 erros, o que mostra que
apenas oito por cento dos acessos não foram bem sucedidos, isso mostra como o
servidor aguentou bem nossos mil usuários, vale lembrar que são mil acessos
simultâneos por segundo, sendo que pode ser que não seja exatamente sincronizado que
no mesmo segundo os mil usuários consigam acessar. Também podemos ver na Figura
12 o número total de usuários que acessaram o site, que confere se juntarmos os testes
bem sucedidos com os erros. Outro parâmetro importante é o TPS (Transações por
Segundo) que foi de 1580, isso significa que mesmo mil usuários acessando o site ainda
temos 580 transações que poderiam ser convertidos em novos usuários. Algumas
estatísticas de tempo médio também são importantes por mostrar como anda a
performance do nosso servidor e do site, como o Mean Time, Meam Response Length,
entre outros.
Conclusão
O The Grinder é uma boa opção para os testes de carga, ele possui suporte para
diversos protocolos e métodos de teste. Ele não possui a melhor interface, isso dificulta
em um primeiro momento o contato com a ferramenta para quem nunca utilizou esse
tipo de software, mas depois de algumas horas testando a ferramenta você acaba
acostumando. Um ponto que pode dificultar um pouco a utilização dele é o fato de você
ter que criar os scripts para o teste e principalmente por esses scripts ter que ser escritos
em Jython, isso expande a capacidade da criação de testes, mas não oferece uma
alternativa mais simples para os mais leigos, mas por outro lado ele oferece uma ótima
documentas e ótimos exemplos de uso, trazendo diversos scripts prontos.
Referências
MAGNANI Mauricio Jr, The Grinder – Um Framework Java Para Testes
de Carga. Disponível em: <http://jbossdivers.wordpress.com/2012/09/30/thegrinder-um-framework-java-para-testes-de-carga/>. Acessado em: 03/12/2013.
ASTON Philip, FITZGERALD Calum, User Guide. Disponivel
<http://grinder.sourceforge.net/g3/manual.html>. Acessado em 28/11/2013.
em:
ROCHA Fabio Gomes, A importância dos testes para a qualidade do software.
Disponivel em: < http://www.devmedia.com.br/a-importancia-dos-testes-para-aqualidade-do-software/28439> Acessado em 09/12/2013.
LOPES Mateus Bruno Teixeira, CARNEIRO Allan Guerreiro. A Importância do
Processo de Teste de Software em TI. Disponível em: < http://www.univicosa
.com.br/arquivos_internos/artigos/ImportanciadoProcessodeTestedeSoftwareemTI.pdf
>, Acessado em 07/12/2013.
SILVA Renata Milena dos Anjos. A importância dos Testes de Qualidade no
desenvolvimento de Software. Disponível em: <http://portal.ftc.br/eventos/
wie/2012/artigos/8%20-%20A%20import%C3%A2ncia%20dos%20Testes%20de
%20Qualidade%20no%20%20desenvolvimento%20de%20Software.pdf>.
Acessado
em: 08/12/2013.
NETO Oscar Nogueira. Qual a diferença entre teste de carga, stress e
performance?. Disponível em: <http://jmeter.com.br/2013/04/qual-a-diferenca-entreteste-de-stress-performance-e-carga/>. Acessado em: 08/12/2013.
Eric Woods. The Grinder – Load Testing Web Applications. Disponivel em:
<http://blog.credera.com/technology-insights/java/the-grinder-load-testing-webapplications/>. Acessado em 11/12/2013.

Documentos relacionados