Dev e QA no mesmo time: critérios de aceite, DoD e shift-left

Neste guia, você vai ver como dev e QA podem trabalhar como um time só, e não como duas etapas de uma esteira. Vamos falar de critérios de aceite claros no formato Dado/Quando/Então, de uma Definition of Done que inclui testes, da pirâmide de testes, de como levar o QA para o refinamento, de PRs com evidência e passos de teste, de testes de unidade que cobrem as bordas, de bug reports úteis e de ambientes reproduzíveis. No fim, há um template de PR pronto para copiar.

Por que “shift-left” é um assunto de dev

Shift-left é a ideia de trazer a qualidade para o começo do ciclo: discutir riscos antes de codar, testar enquanto se desenvolve e não deixar a descoberta de problemas para o fim da sprint. Trabalho com QA desde 2010 e o padrão se repete: quando o teste só acontece depois do “pronto”, o bug volta para o dev com o contexto já esfriado, a correção custa mais e a relação entre as pessoas desgasta. Quando dev e QA conversam cedo, boa parte desses bugs nem chega a ser escrita.

Isso não significa que o dev vira QA ou que o QA vira dev. Significa que qualidade é responsabilidade compartilhada, e que cada um contribui com o que faz melhor: o dev conhece o código e suas fragilidades; o QA enxerga o comportamento, os riscos de negócio e os caminhos que ninguém pensou.

Critérios de aceite claros com Dado/Quando/Então

Um critério de aceite responde a uma pergunta simples: como saberemos que essa história está pronta do ponto de vista de quem usa? O formato Dado/Quando/Então (o Given/When/Then do BDD) ajuda porque obriga a descrever contexto, ação e resultado observável, sem espaço para “deve funcionar corretamente”.

Funcionalidade: Login na loja

  Cenário: Usuário bloqueado tenta entrar
    Dado que existe o usuário "locked_user" com status bloqueado
    Quando ele informa usuário e senha válidos e clica em "Entrar"
    Então a mensagem "Usuário bloqueado" é exibida
    E ele permanece na tela de login
    E nenhum token de sessão é gerado

  Cenário: Senha em branco
    Dado que estou na tela de login
    Quando informo o usuário "standard_user" e deixo a senha vazia
    Então o botão "Entrar" fica desabilitado

Algumas regras práticas para critérios que realmente ajudam:

  • Um comportamento por cenário. Se o “Então” tem cinco coisas sem relação, provavelmente são cenários diferentes.
  • Resultado observável. Mensagem na tela, status HTTP, registro no banco, evento publicado. Algo que dá para verificar.
  • Inclua os caminhos tristes. Erro de validação, permissão negada, timeout, lista vazia. É ali que mora a maioria dos bugs.
  • Linguagem de negócio, não de implementação. “Clico em Entrar” envelhece melhor que “chamo o endpoint X com o payload Y”.

Definition of Done que inclui testes

Critério de aceite é específico de cada história. A Definition of Done (DoD) é o acordo do time que vale para todas. Se a DoD diz só “código revisado e mergeado”, o teste vira opcional na prática. Uma DoD que puxa a qualidade para dentro do desenvolvimento costuma ter itens como:

  • Todos os critérios de aceite verificados, com evidência no PR.
  • Testes de unidade escritos para a lógica nova, incluindo casos de borda.
  • Testes de integração ou de API atualizados quando o contrato mudou.
  • Pipeline verde, sem testes desabilitados “temporariamente” sem justificativa.
  • Nenhum bug crítico ou alto em aberto relacionado à história.
  • Documentação (OpenAPI, README, notas de release) atualizada quando aplicável.

O importante é que a DoD seja curta, visível e revisada pelo time de tempos em tempos. Item que ninguém cumpre deve ser discutido, não ignorado em silêncio.

A pirâmide de testes como mapa de responsabilidades

A pirâmide de testes sugere muitos testes rápidos e baratos na base, uma camada intermediária de integração e poucos testes de ponta a ponta no topo. Ela não é uma regra matemática, mas é um bom mapa para dividir o trabalho:

  • Base (unidade): responsabilidade natural do dev. Valida regras, cálculos, validações e bordas sem subir banco ou navegador.
  • Meio (integração e API): território compartilhado. Contratos entre serviços, persistência, autenticação, mensagens de erro da API.
  • Topo (E2E e exploratório): onde o QA costuma liderar, cobrindo fluxos críticos de ponta a ponta e explorando o que nenhum script previu.

O anti-padrão clássico é a “casquinha de sorvete”: quase nada de unidade e uma montanha de testes de interface lentos e instáveis. Se uma regra pode ser testada na unidade, teste ali e deixe o E2E para provar que as peças se conectam.

Envolver o QA no refinamento

O momento mais barato para encontrar um bug é antes de ele existir. No refinamento, o QA faz as perguntas que transformam uma história vaga em algo testável: “e se o usuário estiver bloqueado?”, “qual o limite de caracteres?”, “o que acontece se a API demorar?”, “isso vale para mobile também?”.

Uma prática que funciona bem é a conversa a três (às vezes chamada de “três amigos”): produto, dev e QA olham juntos a história e saem com os critérios de aceite escritos. Se o time não tem cerimônia formal para isso, quinze minutos antes de puxar a história já resolvem muito.

Testes de unidade que cobrem as bordas

Teste de unidade que só valida o caminho feliz dá falsa segurança. Os bugs aparecem nas bordas, então vale ter um checklist mental:

  • Valores limite: mínimo, máximo, um a menos, um a mais.
  • Vazios e nulos: string vazia, lista vazia, campo ausente, null.
  • Formatos: acentos, emojis, espaços extras, CPF com e sem máscara.
  • Datas e fusos: virada de dia, ano bissexto, horário de verão, timestamps em segundos versus milissegundos.
  • Erros esperados: a função lança a exceção certa com a mensagem certa?

Testes parametrizados ajudam a cobrir essas variações sem duplicar código. Para montar a massa, o gerador de massa de testes e o validador de CPF poupam tempo, e o conversor de timestamp ajuda a conferir os casos de data.

PR com evidência e passos de teste

Um PR que diz só “corrige bug do login” obriga o revisor e o QA a redescobrir tudo. Um bom PR conta o que mudou, por que mudou, como testar e mostra que já foi testado. A evidência pode ser um print, um GIF curto, a saída do teste, um request e response da API ou um trecho de log. Para payloads grandes, formatar com o formatador de JSON ou mostrar o antes e depois com o comparador de JSON deixa a revisão muito mais rápida.

Este é um template que você pode salvar como .github/pull_request_template.md (ou o equivalente da sua plataforma):

## O que muda
<!-- Resumo em 1 a 3 linhas. Link da história/ticket. -->

## Por que
<!-- Contexto do problema ou da regra de negócio. -->

## Critérios de aceite atendidos
- [ ] Cenário 1: ...
- [ ] Cenário 2: ...

## Como testar
1. Ambiente/branch: ...
2. Pré-condições (usuário, massa, feature flag): ...
3. Passos: ...
4. Resultado esperado: ...

## Evidências
<!-- Prints, GIF, saída dos testes, request/response, logs. -->

## Testes automatizados
- [ ] Unidade (incluindo casos de borda)
- [ ] Integração/API
- [ ] E2E (se aplicável)

## Riscos e impacto
<!-- Áreas afetadas, migrações, flags, rollback. -->

## Checklist da DoD
- [ ] Pipeline verde
- [ ] Sem testes desabilitados sem justificativa
- [ ] Documentação atualizada

Como escrever um bug report que ajuda

Bug report bom é aquele que o dev consegue reproduzir na primeira tentativa, sem precisar perguntar nada. O mínimo:

  • Título descritivo: onde, o quê e em que condição. “Checkout aceita cupom expirado no front React” diz muito mais que “cupom com erro”.
  • Ambiente: URL, versão ou commit, navegador, dispositivo, usuário usado.
  • Passos numerados a partir de um estado conhecido.
  • Resultado esperado versus obtido, de preferência ligado ao critério de aceite.
  • Evidências: print, vídeo, request/response, log com ID de correlação.
  • Frequência e severidade: acontece sempre ou às vezes? Bloqueia o fluxo?

Logs gigantes em uma linha só atrapalham; o formatador de log deixa tudo legível. Se o problema envolve autenticação, o decodificador de JWT mostra rapidamente claims e expiração do token, mas lembre de nunca colar tokens reais de produção em tickets.

Ambiente reproduzível

“Na minha máquina funciona” é o sintoma de um ambiente que ninguém controla. Para dev e QA enxergarem o mesmo comportamento, vale investir em:

  • Infra como código local: containers e um arquivo de composição que sobe aplicação, banco e dependências com um comando.
  • Versões fixadas: runtime, dependências e imagens com versão explícita, não “latest”.
  • Massa de dados versionada: scripts de seed que criam sempre os mesmos usuários e cenários.
  • Ambientes efêmeros por PR, quando possível, para o QA testar a mudança isolada antes do merge.
  • Feature flags documentadas: o PR diz qual flag ligar para ver a mudança.

Se você quer treinar tudo isso sem montar nada, o Lab do AppsQuality oferece uma loja de prática em vários fronts e a API de prática tem usuários fixos como standard_user e locked_user, ótimos para escrever critérios de aceite e reproduzir cenários.

Próximos passos

Escolha uma prática para começar na próxima sprint: talvez o template de PR, talvez os critérios em Dado/Quando/Então no refinamento. Para colocar em prática: