Um sistema que passa em todos os testes funcionais ainda pode cair na primeira promoção da Black Friday. "Funciona" responde se o resultado está certo para um usuário; "aguenta" responde se ele continua certo, e rápido, para milhares ao mesmo tempo. Teste de performance é a disciplina que mede essa segunda pergunta — e quase sempre é deixada para a véspera do lançamento, quando já é tarde para mudar a arquitetura.
Performance funcional não é performance sob carga
Abrir a página e ela responder em 200 ms diz pouco. O que importa é como esse número se comporta quando 500 pessoas abrem a mesma página no mesmo segundo. Sistemas degradam de forma não-linear: dobrar a carga raramente dobra o tempo de resposta — em algum ponto uma fila enche, um pool de conexões esgota e a latência dispara de um penhasco. Teste de performance existe para achar esse penhasco antes que o usuário ache.
Os quatro tipos que você precisa distinguir
"Teste de performance" é um guarda-chuva. Por baixo dele há perguntas diferentes, e cada tipo de teste responde a uma:
| Tipo | Pergunta que responde | Como é o perfil de carga |
|---|---|---|
| Carga (load) | Aguenta a carga esperada com tempos aceitáveis? | Sobe até o pico previsto e mantém |
| Estresse (stress) | Onde fica o limite e como ele quebra? | Sobe além do esperado até falhar |
| Soak (endurance) | Degrada com o tempo? (vazamento de memória) | Carga moderada por horas |
| Spike | Sobrevive a um pico súbito e se recupera? | Salto abrupto e queda |
O erro comum é rodar só o teste de carga e declarar vitória. É o teste de estresse que revela como o sistema morre — com um erro limpo de "serviço indisponível" ou travando tudo até o timeout? — e o soak que pega o vazamento de memória que só aparece na terceira hora de produção.
As métricas que importam (e a que engana)
A métrica que mais aparece em dashboard é a que menos diz: a média de tempo de resposta. A média esconde os piores casos. Se 95 requisições respondem em 100 ms e 5 levam 10 segundos, a média fica em ~600 ms — um número que ninguém de fato experimentou. O que importa são os percentis.
O percentil 95 (p95) é o tempo abaixo do qual 95% das requisições ficaram. Dizer "p95 = 800 ms" significa que 1 a cada 20 usuários esperou mais que isso. Em escala, a cauda (p99, p99.9) é onde vivem seus clientes mais irritados — e, como cada página faz dezenas de chamadas, a chance de um usuário cair na cauda em pelo menos uma delas é alta.
| Métrica | O que mede |
|---|---|
| Latência p50 / p95 / p99 | Tempo de resposta típico e de cauda |
| Throughput (req/s) | Quantas requisições o sistema completa por segundo |
| Taxa de erro (%) | Fração de respostas com falha ou timeout |
| Usuários concorrentes | Quantos clientes ativos ao mesmo tempo |
Reporte percentis, nunca a média. A média é a métrica que faz um sistema parecer saudável enquanto 5% dos seus usuários estão olhando para uma tela travada.
O formato típico de um resultado
Quando você sobe a carga aos poucos, o gráfico tem quase sempre a mesma forma: o throughput cresce junto com a carga enquanto a latência fica plana — até o joelho da curva, o ponto em que o sistema satura. A partir daí, mais carga não traz mais throughput; só empilha latência e, logo depois, erros.
tempo de
resposta
│ ╱ ← latência dispara
│ ╱ (saturado)
│ _____╱
│ joelho da curva ___╱
│ ___________________╱
│ '
└────────────────────────────────────▶ usuários concorrentes
▲
capacidade útil
O objetivo do teste é localizar esse joelho e garantir que ele fica acima do seu pico de tráfego esperado, com folga. A capacidade útil não é onde o sistema quebra — é onde ele ainda responde dentro do seu acordo de latência.
Um plano mínimo no JMeter
O JMeter é a ferramenta clássica para isso: open-source, baseada em planos de teste. A anatomia mínima é um Thread Group (define quantos usuários virtuais e em quanto tempo eles entram — o ramp-up), um ou mais Samplers (as requisições HTTP) e os Listeners que coletam os resultados.
Test Plan
└─ Thread Group (100 usuários, ramp-up 30s, loop 10x)
├─ HTTP Request GET /api/produtos
├─ HTTP Request POST /api/carrinho
├─ Assertion status == 200 && tempo < 1s
└─ Listener Summary Report (p90/p95, throughput, %erro)
Detalhe que separa um teste honesto de um teatro de números: o ramp-up. Jogar 100 usuários no mesmo instante mede um pico artificial; subir 100 ao longo de 30 segundos imita a chegada real e mostra a degradação acontecendo. E rode a carga de uma máquina com rede e CPU sobrando — se o gargalo for o seu próprio gerador de carga, você está medindo o laptop, não o servidor.
Na prática, o teste roda em modo non-GUI (a interface gráfica do JMeter consome recurso demais para gerar carga séria) e vira um passo do pipeline, o que conecta direto com testes no CI/CD — um job noturno que falha o build se o p95 regredir.
Defina o critério antes de rodar
Um teste de performance sem um número-alvo é só geração de gráfico bonito. Antes de subir a carga, escreva o critério de aceite em forma de SLO: algo como "p95 abaixo de 500 ms e taxa de erro abaixo de 0,1% com 200 usuários concorrentes". Com isso definido, o resultado deixa de ser "achei o sistema meio lento" e vira um veredito objetivo — passou ou não passou. É a mesma lógica de um bom relatório de bug: sem o esperado, não há defeito, só opinião.
Conclusão
Teste de performance responde à pergunta que o teste funcional não faz: o sistema continua correto e rápido sob carga real? Escolha o tipo certo (carga para validar o esperado, estresse para achar o limite, soak para o vazamento, spike para o susto), reporte percentis em vez de médias, ache o joelho da curva e ancore tudo num SLO definido antes de começar. Feito cedo e dentro do pipeline, ele troca a surpresa da véspera de lançamento por um número que você acompanha o tempo todo.
A system that passes every functional test can still go down on the first Black Friday promotion. "It works" answers whether the result is right for one user; "it holds up" answers whether it stays right, and fast, for thousands at the same time. Performance testing is the discipline that measures that second question — and it is almost always left for the eve of launch, when it's too late to change the architecture.
Functional performance is not performance under load
Opening the page and getting a response in 200 ms says little. What matters is how that number behaves when 500 people open the same page in the same second. Systems degrade non-linearly: doubling the load rarely doubles the response time — at some point a queue fills up, a connection pool runs dry and latency falls off a cliff. Performance testing exists to find that cliff before the user does.
The four types you need to tell apart
"Performance testing" is an umbrella term. Underneath it are different questions, and each type of test answers one:
| Type | Question it answers | What the load profile looks like |
|---|---|---|
| Load | Does it handle the expected load with acceptable times? | Ramps up to the expected peak and holds |
| Stress | Where is the limit and how does it break? | Goes beyond the expected until it fails |
| Soak (endurance) | Does it degrade over time? (memory leak) | Moderate load for hours |
| Spike | Does it survive a sudden peak and recover? | Abrupt jump and drop |
The common mistake is running only the load test and declaring victory. It's the stress test that reveals how the system dies — with a clean "service unavailable" error or by hanging everything until timeout? — and the soak test that catches the memory leak that only shows up in the third hour of production.
The metrics that matter (and the one that misleads)
The metric that appears most on dashboards is the one that says the least: the average response time. The average hides the worst cases. If 95 requests respond in 100 ms and 5 take 10 seconds, the average comes out around ~600 ms — a number nobody actually experienced. What matters are the percentiles.
The 95th percentile (p95) is the time below which 95% of requests fell. Saying "p95 = 800 ms" means 1 in 20 users waited longer than that. At scale, the tail (p99, p99.9) is where your angriest customers live — and since each page makes dozens of calls, the chance of a user landing in the tail on at least one of them is high.
| Metric | What it measures |
|---|---|
| Latency p50 / p95 / p99 | Typical and tail response time |
| Throughput (req/s) | How many requests the system completes per second |
| Error rate (%) | Fraction of responses that failed or timed out |
| Concurrent users | How many clients are active at the same time |
Report percentiles, never the average. The average is the metric that makes a system look healthy while 5% of your users are staring at a frozen screen.
The typical shape of a result
When you ramp up the load gradually, the chart almost always has the same shape: throughput grows along with the load while latency stays flat — until the knee of the curve, the point where the system saturates. From there on, more load brings no more throughput; it only stacks up latency and, soon after, errors.
response
time
│ ╱ ← latency shoots up
│ ╱ (saturated)
│ _____╱
│ knee of the curve ___╱
│ ___________________╱
│ '
└────────────────────────────────────▶ concurrent users
▲
usable capacity
The goal of the test is to locate that knee and make sure it sits above your expected traffic peak, with headroom. Usable capacity isn't where the system breaks — it's where it still responds within your latency agreement.
A minimal JMeter plan
JMeter is the classic tool for this: open-source, built around test plans. The minimal anatomy is a Thread Group (defines how many virtual users and over how long they join — the ramp-up), one or more Samplers (the HTTP requests) and the Listeners that collect the results.
Test Plan
└─ Thread Group (100 users, ramp-up 30s, loop 10x)
├─ HTTP Request GET /api/produtos
├─ HTTP Request POST /api/carrinho
├─ Assertion status == 200 && time < 1s
└─ Listener Summary Report (p90/p95, throughput, %error)
A detail that separates an honest test from number theater: the ramp-up. Throwing 100 users in at the same instant measures an artificial spike; bringing in 100 over 30 seconds mimics real arrival and shows the degradation happening. And run the load from a machine with network and CPU to spare — if the bottleneck is your own load generator, you're measuring the laptop, not the server.
In practice, the test runs in non-GUI mode (JMeter's graphical interface consumes too many resources to generate serious load) and becomes a pipeline step, which ties directly into testing in CI/CD — a nightly job that fails the build if p95 regresses.
Define the criterion before you run
A performance test without a target number is just pretty chart generation. Before ramping up the load, write the acceptance criterion as an SLO: something like "p95 under 500 ms and error rate under 0.1% with 200 concurrent users". With that defined, the result stops being "I found the system kind of slow" and becomes an objective verdict — pass or fail. It's the same logic as a good bug report: without the expected result, there's no defect, only opinion.
Conclusion
Performance testing answers the question functional testing doesn't: does the system stay correct and fast under real load? Pick the right type (load to validate the expected, stress to find the limit, soak for the leak, spike for the scare), report percentiles instead of averages, find the knee of the curve and anchor everything in an SLO defined before you start. Done early and inside the pipeline, it swaps the surprise on the eve of launch for a number you track all along.