QA
Sistema Login QA
Ambiente Online Disponível em https://qa.sauer.pro.br

Documentação & Laboratório de QA

Aprenda a arquitetura completa da aplicação, consuma seus endpoints e realize testes práticos de Carga e Stress com Apache JMeter apontando para o ambiente online seguro ou local.

🔒 HTTPS / SSL Seguro 🌐 qa.sauer.pro.br 🐍 Python / Flask 🗄️ SQLite Database 🐳 Docker Container ⚡ Apache JMeter
1

Arquitetura da Solução

A aplicação Sistema Login QA foi implantada em ambiente com terminação SSL segura (https://qa.sauer.pro.br), operando com containerização Docker, backend em microframework Flask e persistência relacional em SQLite.

Diagrama de Fluxo e Topologia de Rede

👤
Cliente / QA
Navegador Web
Apache JMeter
Postman / cURL
🔒
Proxy Reverso / SSL
Certificado Let's Encrypt
Encaminhamento de Portas
🐍
Flask + SQLite
app.py (Porta 5000)
banco.db (SQLite)
Camada / Módulo Tecnologia Endereçamento / Arquivo Função no Sistema
Ambiente de Produção (Online) HTTPS / TLS 1.3 https://qa.sauer.pro.br Servidor em nuvem acessível publicamente para a realização dos testes de QA.
Interface Web (Frontend) HTML5 + Tailwind CSS + Jinja2 templates/login.html Interface responsiva com mensagens flash para feedback de autenticação.
Servidor de Aplicação (Backend) Python 3.11 / Flask app.py Processa requisições HTTP, valida credenciais e gerencia rotas.
Banco de Dados Relacional SQLite 3 banco.db Armazena tabela usuarios e usuário padrão admin / 123456.
Containerização Docker & Docker Compose Dockerfile / docker-compose.yml Isola a aplicação mapeando internamente a porta 5555:5000.
Ambientes Disponíveis para Testes:
  • Ambiente Online Oficial (Recomendado): https://qa.sauer.pro.br (Protocolo: https | Servidor: qa.sauer.pro.br | Porta: 443)
  • Ambiente Local (Docker Compose): http://localhost:5555
  • Ambiente Local (Python direto): http://localhost:5000
2

APIs e Rotas da Solução

Abaixo estão documentadas todas as rotas HTTP da aplicação disponíveis no servidor https://qa.sauer.pro.br.

GET /
https://qa.sauer.pro.br/

Descrição:

Retorna o formulário web com a tela de login estilizada.

Respostas Possíveis:

200 OK Retorna o código HTML do arquivo templates/login.html contendo o título Sistema Login QA.

Exemplo cURL (Online):

curl -X GET https://qa.sauer.pro.br/
POST /login
https://qa.sauer.pro.br/login

Descrição:

Processa o login do usuário via dados de formulário (application/x-www-form-urlencoded) e consulta a tabela no banco SQLite.

Parâmetros do Corpo (Form Body):

Campo Tipo Obrigatório? Credencial Padrão de Teste
usuario String Sim admin
senha String Sim 123456

Respostas Possíveis:

200 OK Login com Sucesso: Retorna HTML com <h1>Bem-vindo, admin! Login realizado com sucesso.</h1>.
302 Found Login com Falha: Dispara mensagem flash "Usuário ou senha inválidos." e redireciona para /.

Exemplos cURL (Online):

# 1. Testar Login com Sucesso
curl -X POST https://qa.sauer.pro.br/login -d "usuario=admin&senha=123456"
# 2. Testar Login com Senha Incorreta
curl -X POST https://qa.sauer.pro.br/login -d "usuario=admin&senha=senha_errada"
GET /documentacao
https://qa.sauer.pro.br/documentacao

Retorna este portal interativo com as instruções passo a passo, catálogo de APIs e laboratórios de teste de carga/stress.

3

Exercício 01: Testes de Carga e Stress com Apache JMeter

Laboratório Prático Passo a Passo (Ambiente https://qa.sauer.pro.br)

🎯 Conceito Fundamental: Carga vs. Stress

Para que você não confunda os dois testes durante a atividade, veja a metáfora prática abaixo:

🏋️ 1. Teste de Carga (Load Test)
Objetivo: Validar o sistema sob uso normal diário.

Metáfora do Elevador: Se um elevador suporta 8 pessoas, colocamos 5 a 8 pessoas para subir e medimos se ele chega ao andar no tempo prometido.

👉 Nosso Teste: 20 a 50 usuários virtuais acessando https://qa.sauer.pro.br/login. A resposta deve ser rápida (< 300ms) e com 0.00% de erros.
💥 2. Teste de Stress (Stress Test)
Objetivo: Descobrir o limite onde o sistema falha (Ponto de Ruptura).

Metáfora do Elevador: Colocamos 30 pessoas espremidas até o alarme disparar ou o elevador parar de responder.

👉 Nosso Teste: 300 a 1000 usuários simultâneos bombardeando o servidor para ver o tempo de resposta subir e o servidor atingir o limite.
⚡ 1. Ainda não tem o JMeter ou Java?

Passo a passo ilustrado para baixar o Java, o arquivo ZIP do JMeter e descompactar.

Guia de Instalação ➔
🎓 2. Quer aprender a operar o JMeter?

Mini curso detalhado explicando cada menu, componente e como interpretar os resultados no nosso laboratório.

Acessar Mini Curso do JMeter ➔
Resumo Rápido para quem já tem o JMeter descompactado:
  1. Abra a pasta onde você descompactou o Apache JMeter (ex: apache-jmeter-5.6.3).
  2. Entre na pasta bin.
  3. No Windows: Dê duplo clique no arquivo jmeter.bat (ou no Linux/Mac execute ./jmeter.sh no terminal).
  4. Aguarde alguns segundos e a interface gráfica do JMeter será exibida.
1

Passo 1: Criar o Grupo de Usuários Virtuais (Thread Group)

Cada Thread representa um usuário virtual simulando acessos reais ao site https://qa.sauer.pro.br.

Caminho no Menu do JMeter:
  1. Clique com o botão direito em Test Plan (Plano de Teste).
  2. Passe o mouse sobre Add ➔ Threads (Users) ➔ Clique em Thread Group.
  3. No campo Name, digite: Grupo_Usuarios_QA.
Campo na Tela O que significa? Valor para TESTE DE CARGA Valor para TESTE DE STRESS
Number of Threads (users) Número de pessoas virtuais disparando requisições. 20 300 a 500
Ramp-up period (seconds) Tempo em segundos para que todas as threads entrem em ação de forma escalonada. 5 10
Loop Count Quantas vezes seguidas cada usuário repetirá o teste. 5 10
2

Passo 2: Configurar a Requisição HTTP Apontando para https://qa.sauer.pro.br

Configure o JMeter para chamar a rota de autenticação segura (POST /login).

Passo a Passo na Tela do JMeter:
  1. Clique com o botão direito em Grupo_Usuarios_QA.
  2. Passe o mouse em Add ➔ Sampler ➔ Clique em HTTP Request.
  3. Preencha os campos exatamente como abaixo:
Name (Nome da Requisição) POST /login (qa.sauer.pro.br)
Protocol [http] https
Server Name or IP qa.sauer.pro.br
Port Number 443 (ou em branco)
HTTP Method POST
Path (Caminho) /login
Na aba inferior "Parameters", clique duas vezes no botão "Add" e insira:
Name: usuario Value: admin
Name: senha Value: 123456
3

Passo 3: Validar a Resposta com Asserção (Response Assertion)

A asserção garante que o JMeter validará automaticamente se a frase de boas-vindas foi retornada no corpo da página.

  1. Clique com o botão direito em cima de POST /login (qa.sauer.pro.br).
  2. Passe o mouse em Add ➔ Assertions ➔ Clique em Response Assertion.
  3. Em Field to Test, deixe marcado Text Response.
  4. Em Pattern Matching Rules, marque Substring.
  5. Na caixa Patterns to Test, clique no botão Add e digite: Bem-vindo, admin!
4

Passo 4: Adicionar Ouvintes para Visualizar Métricas (Listeners)

Adicione os dois principais ouvintes para acompanhar os resultados em tempo real:

🌲 1. View Results Tree (Árvore de Resultados)

Exibe cada requisição individual com sinal verde (sucesso) ou vermelho (erro) e permite inspecionar o HTML retornado.

Botão direito em Grupo_Usuarios_QA ➔ Add ➔ Listener ➔ View Results Tree
📊 2. Summary Report (Relatório de Resumo)

Gera as métricas estatísticas consolidadas: Média de latência (ms), % de erro e vazão de requisições por segundo.

Botão direito em Grupo_Usuarios_QA ➔ Add ➔ Listener ➔ Summary Report
5

Passo 5: Executando o Teste e Preenchendo o Relatório de QA

Para iniciar o teste no JMeter, clique no botão verde com ícone de reprodução ▶ (Start) no topo. Para limpar os dados antes de uma nova rodada, clique no ícone de duas vassouras 🧹 Clear All.

Coluna no Summary Report O que você observará no TESTE DE CARGA O que você observará no TESTE DE STRESS Conclusão de QA
Samples (Amostras) 100 (20 threads × 5 loops) 5.000 (500 threads × 10 loops) Total de requisições enviadas ao servidor.
Average (Tempo Médio) Baixo (ex: 20ms a 150ms) Aumenta para 2.000ms a 10.000ms+ No stress o servidor enfileira os acessos, gerando lentidão visível.
Error % (Taxa de Erros) 0.00% Aparecem erros (10% a 50%+) Sobrecarga gera Gateway Timeout (504), Bad Gateway (502) ou lock no SQLite.
Throughput (Vazão) Vazão estável em req/seg Atinge teto e depois despenca Identifica a capacidade máxima de processamento do hardware.
4

Plano de Testes JMX Pré-configurado

Pronto para https://qa.sauer.pro.br

Copie o arquivo XML abaixo ou utilize o arquivo teste_login_jmeter.jmx na raiz do projeto e abra diretamente no JMeter em File ➔ Open.

teste_login_qa_sauer.jmx (Apontando para https://qa.sauer.pro.br)
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
  <hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Plano de Testes - https://qa.sauer.pro.br" enabled="true">
      <stringProp name="TestPlan.comments">Laboratorio de Carga e Stress - Cotemig QA</stringProp>
      <boolProp name="TestPlan.functional_mode">false</boolProp>
      <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="Variaveis do Usuario" enabled="true">
        <collectionProp name="Arguments.arguments"/>
      </elementProp>
      <stringProp name="TestPlan.user_define_classpath"></stringProp>
    </TestPlan>
    <hashTree>
      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Grupo_Usuarios_QA" enabled="true">
        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Controlador de Loop" enabled="true">
          <boolProp name="LoopController.continue_forever">false</boolProp>
          <stringProp name="LoopController.loops">5</stringProp>
        </elementProp>
        <stringProp name="ThreadGroup.num_threads">20</stringProp>
        <stringProp name="ThreadGroup.ramp_time">5</stringProp>
        <boolProp name="ThreadGroup.scheduler">false</boolProp>
        <stringProp name="ThreadGroup.duration"></stringProp>
        <stringProp name="ThreadGroup.delay"></stringProp>
        <boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
      </ThreadGroup>
      <hashTree>
        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST /login (qa.sauer.pro.br)" enabled="true">
          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variaveis" enabled="true">
            <collectionProp name="Arguments.arguments">
              <elementProp name="usuario" elementType="HTTPArgument">
                <boolProp name="HTTPArgument.always_encode">false</boolProp>
                <stringProp name="Argument.name">usuario</stringProp>
                <stringProp name="Argument.value">admin</stringProp>
                <stringProp name="Argument.metadata">=</stringProp>
                <boolProp name="HTTPArgument.use_equals">true</boolProp>
              </elementProp>
              <elementProp name="senha" elementType="HTTPArgument">
                <boolProp name="HTTPArgument.always_encode">false</boolProp>
                <stringProp name="Argument.name">senha</stringProp>
                <stringProp name="Argument.value">123456</stringProp>
                <stringProp name="Argument.metadata">=</stringProp>
                <boolProp name="HTTPArgument.use_equals">true</boolProp>
              </elementProp>
            </collectionProp>
          </elementProp>
          <stringProp name="HTTPSampler.domain">qa.sauer.pro.br</stringProp>
          <stringProp name="HTTPSampler.port">443</stringProp>
          <stringProp name="HTTPSampler.protocol">https</stringProp>
          <stringProp name="HTTPSampler.contentEncoding">UTF-8</stringProp>
          <stringProp name="HTTPSampler.path">/login</stringProp>
          <stringProp name="HTTPSampler.method">POST</stringProp>
          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
        </HTTPSamplerProxy>
        <hashTree>
          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Validar Boas-vindas" enabled="true">
            <collectionProp name="Asserion.test_strings">
              <stringProp name="49586">Bem-vindo, admin!</stringProp>
            </collectionProp>
            <stringProp name="Assertion.custom_message"></stringProp>
            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
            <boolProp name="Assertion.assume_success">false</boolProp>
            <intProp name="Assertion.test_type">2</intProp>
          </ResponseAssertion>
          <hashTree/>
        </hashTree>
        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
          <boolProp name="ResultCollector.error_logging">false</boolProp>
          <objProp>
            <name>saveConfig</name>
            <value class="SampleSaveConfiguration">
              <time>true</time>
              <latency>true</latency>
              <timestamp>true</timestamp>
              <success>true</success>
              <label>true</label>
              <code>true</code>
              <message>true</message>
              <threadName>true</threadName>
              <dataType>true</dataType>
              <encoding>false</encoding>
              <assertions>true</assertions>
              <subresults>true</subresults>
              <responseData>false</responseData>
              <samplerData>false</samplerData>
              <xml>false</xml>
              <fieldNames>true</fieldNames>
              <responseHeaders>false</responseHeaders>
              <requestHeaders>false</requestHeaders>
              <responseDataOnError>false</responseDataOnError>
              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
              <assertionsResultsToSave>0</assertionsResultsToSave>
              <bytes>true</bytes>
              <sentBytes>true</sentBytes>
              <url>true</url>
              <threadCounts>true</threadCounts>
              <idleTime>true</idleTime>
              <connectTime>true</connectTime>
            </value>
          </objProp>
          <stringProp name="filename"></stringProp>
        </ResultCollector>
        <hashTree/>
        <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true">
          <boolProp name="ResultCollector.error_logging">false</boolProp>
          <objProp>
            <name>saveConfig</name>
            <value class="SampleSaveConfiguration">
              <time>true</time>
              <latency>true</latency>
              <timestamp>true</timestamp>
              <success>true</success>
              <label>true</label>
              <code>true</code>
              <message>true</message>
              <threadName>true</threadName>
              <dataType>true</dataType>
              <encoding>false</encoding>
              <assertions>true</assertions>
              <subresults>true</subresults>
              <responseData>false</responseData>
              <samplerData>false</samplerData>
              <xml>false</xml>
              <fieldNames>true</fieldNames>
              <responseHeaders>false</responseHeaders>
              <requestHeaders>false</requestHeaders>
              <responseDataOnError>false</responseDataOnError>
              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
              <assertionsResultsToSave>0</assertionsResultsToSave>
              <bytes>true</bytes>
              <sentBytes>true</sentBytes>
              <url>true</url>
              <threadCounts>true</threadCounts>
              <idleTime>true</idleTime>
              <connectTime>true</connectTime>
            </value>
          </objProp>
          <stringProp name="filename"></stringProp>
        </ResultCollector>
        <hashTree/>
      </hashTree>
    </hashTree>
  </hashTree>
</jmeterTestPlan>
5

Perguntas de Fixação & Gabarito

Teste seus conhecimentos
1. Qual é a principal diferença entre Teste de Carga e Teste de Stress? ▼
Resposta: O Teste de Carga avalia como o sistema se comporta sob a quantidade normal ou esperada de usuários no dia a dia. Já o Teste de Stress empurra o sistema além do seu limite planejado para encontrar o ponto exato onde a aplicação trava, fica indisponível ou perde dados (ponto de ruptura).
2. Ao testar um servidor online como https://qa.sauer.pro.br no JMeter, quais campos de conexão devem ser preenchidos? ▼
Resposta: No elemento HTTP Request, deve-se preencher Protocol = https, Server Name or IP = qa.sauer.pro.br, Port = 443 (ou em branco), Method = POST e Path = /login. Não se deve colocar "https://" dentro do campo Server Name.
3. No JMeter, o que significa o campo "Ramp-up Period"? ▼
Resposta: É o tempo (em segundos) que o JMeter leva para colocar todos os usuários virtuais (Threads) para rodar gradualmente. Por exemplo, se configuramos 20 Threads e Ramp-up de 5 segundos, o JMeter iniciará 4 novos usuários a cada segundo.
4. Para que serve o componente "Response Assertion" no JMeter? ▼
Resposta: Ele atua como uma validação automatizada dentro do JMeter. Em vez de apenas verificar se o servidor respondeu HTTP 200, ele analisa se o conteúdo do texto retornado contém a frase esperada (como Bem-vindo, admin!). Se a frase não existir (ex: falha de login), o JMeter marca a requisição como erro (vermelha).