@analizza-ai/testspec 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/bin/cli.js +42 -0
- package/package.json +69 -0
- package/src/adapters/agents/claude.js +88 -0
- package/src/adapters/agents/copilot.js +39 -0
- package/src/adapters/agents/index.js +22 -0
- package/src/adapters/sdd/index.js +23 -0
- package/src/adapters/sdd/openspec.js +58 -0
- package/src/adapters/sdd/speckit.js +19 -0
- package/src/commands/generate.js +66 -0
- package/src/commands/init.js +112 -0
- package/src/commands/report.js +60 -0
- package/src/commands/validate.js +68 -0
- package/src/core/reporter.js +44 -0
- package/src/core/spec-parser.js +141 -0
- package/src/core/stub-generator.js +92 -0
- package/src/core/testcontainers.js +39 -0
- package/src/core/tests-builder.js +120 -0
- package/src/index.js +10 -0
- package/src/utils/config.js +29 -0
- package/src/utils/logger.js +13 -0
- package/src/utils/sdd-detector.js +23 -0
- package/templates/agent-instructions/AGENTS.md +39 -0
- package/templates/agent-instructions/CLAUDE.md +48 -0
- package/templates/agent-instructions/copilot.md +52 -0
- package/templates/agent-instructions/skills/testspec-apply-qa.md +424 -0
- package/templates/agent-instructions/skills/testspec-generate.md +138 -0
- package/templates/agent-instructions/skills/testspec-run-qa.md +338 -0
- package/templates/agent-instructions/skills/testspec-specify-qa.md +535 -0
- package/templates/stubs/jest/unit.template.js +17 -0
- package/templates/stubs/junit/unit.template.java +27 -0
- package/templates/stubs/pytest/unit.template.py +18 -0
- package/templates/stubs/testcontainers/node-pg-kafka.template.js +38 -0
- package/templates/stubs/testcontainers/node-pg.template.js +32 -0
- package/templates/stubs/testcontainers/spring-pg-kafka.template.java +41 -0
- package/templates/stubs/vitest/unit.template.js +19 -0
- package/templates/tests-md/default.md +43 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
# testspec-specify-qa
|
|
2
|
+
|
|
3
|
+
Inicia a especificação técnica de testes QA para uma feature, buscando o `tests.md` via GitHub MCP, conduzindo um questionário com o QA engineer e gerando os artefatos de especificação e estrutura de pastas no diretório atual do projeto QA.
|
|
4
|
+
|
|
5
|
+
## Quando usar
|
|
6
|
+
|
|
7
|
+
Após `/testspec-generate` ter gerado o `tests.md` da feature no repositório do app.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Configuração
|
|
12
|
+
|
|
13
|
+
- **App repo:** acessado exclusivamente via GitHub MCP — owner/repo definido em `./testspec/instructions.md` via campo `app_repo` (ex: `diegolirio/sdd-sdt-flow`)
|
|
14
|
+
- **Nunca use caminhos relativos ou locais** para acessar o repositório do app — sempre GitHub MCP
|
|
15
|
+
- **Diretório base de saída:** **sempre o diretório de trabalho atual** (`.`) — nunca use caminhos absolutos ou hardcoded
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Instructions
|
|
20
|
+
|
|
21
|
+
### 0. Garantir e ler arquivos globais do projeto QA (SEMPRE, primeiro passo)
|
|
22
|
+
|
|
23
|
+
**Nunca peça ao usuário para criar arquivos ou diretórios manualmente. Crie tudo que faltar e prossiga.**
|
|
24
|
+
|
|
25
|
+
#### 0a. Garantir estrutura `./testspec/`
|
|
26
|
+
|
|
27
|
+
Se `./testspec/` não existir, crie o diretório.
|
|
28
|
+
|
|
29
|
+
#### 0b. Garantir `./testspec/instructions.md`
|
|
30
|
+
|
|
31
|
+
Se o arquivo não existir, pergunte ao usuário via **AskUserQuestion** (open-ended):
|
|
32
|
+
> "Qual é o repositório GitHub do app? (formato: owner/repo, ex: diegolirio/sdd-sdt-flow)"
|
|
33
|
+
|
|
34
|
+
Com a resposta, crie o arquivo com conteúdo padrão opinativo:
|
|
35
|
+
|
|
36
|
+
```markdown
|
|
37
|
+
# Instructions — QA Project
|
|
38
|
+
|
|
39
|
+
## app_repo
|
|
40
|
+
{owner/repo informado pelo usuário}
|
|
41
|
+
|
|
42
|
+
## Ferramenta de teste padrão
|
|
43
|
+
k6
|
|
44
|
+
|
|
45
|
+
## Arquitetura
|
|
46
|
+
- Scripts organizados por tipo: e2e/, load/, chaos-engineering/
|
|
47
|
+
- Um script por caso de teste (CT)
|
|
48
|
+
- Run plans .md gerados ao lado de cada script
|
|
49
|
+
|
|
50
|
+
## Tecnologias
|
|
51
|
+
- k6 (JavaScript) para E2E e load
|
|
52
|
+
- kubectl para coleta de logs k8s
|
|
53
|
+
- Confluence para publicação de relatórios
|
|
54
|
+
|
|
55
|
+
## Convenções
|
|
56
|
+
- Nomenclatura kebab-case sem acentos
|
|
57
|
+
- Variáveis de ambiente para URLs e credenciais (nunca hardcode)
|
|
58
|
+
- Thresholds: p95 < 500ms, error rate < 1% (load); p95 < 2000ms, error rate < 5% (chaos)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Após criar, leia o arquivo e aplique todas as diretrizes durante a geração — elas têm precedência sobre os defaults da skill.
|
|
62
|
+
|
|
63
|
+
#### 0c. Garantir `./testspec/current-feature.md`
|
|
64
|
+
|
|
65
|
+
Se o arquivo não existir, crie-o vazio e execute o passo 1 normalmente.
|
|
66
|
+
Se existir e contiver um nome de feature válido, use-o diretamente — pule o passo 1.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
### 1. Identificar a feature
|
|
71
|
+
|
|
72
|
+
O argumento passado pelo usuário pode ser:
|
|
73
|
+
- **Nome da feature** em kebab-case (ex: `kafka-consumer-order-request`)
|
|
74
|
+
- **Nenhum argumento** — siga o passo 1b abaixo
|
|
75
|
+
|
|
76
|
+
> **Atenção:** se `./testspec/current-feature.md` já definiu a feature no passo 0, este passo é ignorado.
|
|
77
|
+
|
|
78
|
+
**Se nenhum argumento foi passado e `current-feature.md` está vazio:**
|
|
79
|
+
|
|
80
|
+
Leia `app_repo` de `./testspec/instructions.md` (ex: `diegolirio/sdd-sdt-flow`).
|
|
81
|
+
|
|
82
|
+
**Prioridade 1 — GitHub MCP**
|
|
83
|
+
|
|
84
|
+
Busque em paralelo via GitHub MCP usando o `app_repo` do `instructions.md`:
|
|
85
|
+
- Features **ativas**: `GET /repos/{app_repo}/contents/openspec/changes` — filtre `type: "dir"`, exclua `archive`
|
|
86
|
+
- Features **arquivadas**: `GET /repos/{app_repo}/contents/openspec/changes/archive` — filtre `type: "dir"`
|
|
87
|
+
|
|
88
|
+
**Prioridade 2 — Fallback: features já em `./testspec/`**
|
|
89
|
+
|
|
90
|
+
Se o GitHub MCP falhar (sem token, sem acesso), liste os diretórios em `./testspec/` excluindo `instructions.md` e `current-feature.md`. Prefixe com `[local]`.
|
|
91
|
+
|
|
92
|
+
Se nenhuma fonte retornar features, informe e encerre:
|
|
93
|
+
```
|
|
94
|
+
Nenhuma feature encontrada em {app_repo} via GitHub MCP nem em ./testspec/.
|
|
95
|
+
Verifique o token GitHub ou adicione features ao repositório.
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
a. Monte a lista completa e pergunte ao usuário via **AskUserQuestion** (single select):
|
|
99
|
+
> "Qual feature deseja especificar os testes QA?"
|
|
100
|
+
|
|
101
|
+
- Prefixe: `[ativa]`, `[arquivada]` ou `[local]`
|
|
102
|
+
- Exiba o nome exato do diretório
|
|
103
|
+
|
|
104
|
+
b. Registre: nome da feature e sua origem.
|
|
105
|
+
|
|
106
|
+
### 1.1 Fixar a feature em `current-feature.md`
|
|
107
|
+
|
|
108
|
+
Independentemente de como a feature foi identificada (argumento, seleção ou `current-feature.md` já preenchido), **sempre escreva** o nome da feature no arquivo:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
./testspec/current-feature.md
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Conteúdo do arquivo (apenas o nome, sem formatação extra):
|
|
115
|
+
```
|
|
116
|
+
{feature-name}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Isso garante que `/testspec-apply-qa` e outras skills da sessão usem a mesma feature sem precisar perguntar novamente.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### 2. Ler o `tests.md`
|
|
124
|
+
|
|
125
|
+
Leia via GitHub MCP usando `app_repo` de `instructions.md`:
|
|
126
|
+
- Feature **ativa**: path `openspec/changes/{feature-name}/tests.md`
|
|
127
|
+
- Feature **arquivada**: path `openspec/changes/archive/{feature-name}/tests.md`
|
|
128
|
+
|
|
129
|
+
O conteúdo virá em base64 — decodifique antes de usar.
|
|
130
|
+
|
|
131
|
+
**Fallback — feature `[local]`** (veio de `./testspec/`):
|
|
132
|
+
|
|
133
|
+
Se a feature foi encontrada apenas em `./testspec/` e o GitHub MCP está inacessível, verifique se `spec.qa.md` já existe em `./testspec/{feature-name}/`:
|
|
134
|
+
- Se existe: pergunte via **AskUserQuestion**:
|
|
135
|
+
> "Encontrei `spec.qa.md` existente para esta feature. Como deseja prosseguir?"
|
|
136
|
+
- **Refazer** — ignora o existente e segue o questionário normalmente
|
|
137
|
+
- **Usar o existente** — pula para o passo 8
|
|
138
|
+
- Se não existe: encerre informando:
|
|
139
|
+
```
|
|
140
|
+
tests.md não encontrado via GitHub MCP.
|
|
141
|
+
Verifique o token GitHub ou execute /testspec-generate no repositório do app.
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### 3. Analisar o `tests.md` em profundidade
|
|
147
|
+
|
|
148
|
+
Extraia e internalize **todos** os seguintes dados antes de avançar:
|
|
149
|
+
|
|
150
|
+
- **Nome legível da feature** — título `# Testes —`
|
|
151
|
+
- **Protocolo de entrada** — HTTP, Kafka, gRPC (derivado de `## Contexto` e `## Estrutura`)
|
|
152
|
+
- **Método HTTP e path** — se protocolo HTTP
|
|
153
|
+
- **Schema do body de entrada** — campos, tipos, obrigatoriedade
|
|
154
|
+
- **Schema da resposta de sucesso** — status HTTP, headers (especialmente `Location`), body
|
|
155
|
+
- **Tabela(s) de banco de dados** — mencionadas em `## Validações de Banco de Dados`
|
|
156
|
+
- **Todos os `### CT-NN`** — número, nome, entrada, critérios de aceite, tipo (sucesso/rejeição)
|
|
157
|
+
- **Regras de negócio implícitas** — ex: "COUNT não muda", "campo preenchido com valor X"
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
### 4. Conduzir o questionário
|
|
162
|
+
|
|
163
|
+
Faça as perguntas **em sequência**, uma de cada vez — não agrupe em uma só mensagem.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
#### Pergunta A — Ferramenta de testes
|
|
168
|
+
|
|
169
|
+
**AskUserQuestion** (single select):
|
|
170
|
+
> "Qual ferramenta será usada para implementar os scripts de teste?"
|
|
171
|
+
|
|
172
|
+
| Opção | Descrição |
|
|
173
|
+
|---|---|
|
|
174
|
+
| **K6** | JavaScript, ideal para E2E e load; extensão `.js` |
|
|
175
|
+
| **Gatling** | Scala/Java, foco em load e relatórios detalhados; extensão `.scala` |
|
|
176
|
+
| **Outro** | Abre campo livre para descrever ferramenta e extensão de arquivo |
|
|
177
|
+
|
|
178
|
+
Se "Outro": pergunte em seguida (open-ended) o nome da ferramenta e a extensão dos arquivos.
|
|
179
|
+
|
|
180
|
+
Registre: `{tool}` (ex: `k6`, `gatling`) e `{ext}` (ex: `.js`, `.scala`).
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
#### Pergunta B — Tipos de teste
|
|
185
|
+
|
|
186
|
+
**AskUserQuestion** (multi select):
|
|
187
|
+
> "Quais tipos de teste deseja implementar para esta feature?"
|
|
188
|
+
|
|
189
|
+
| Opção | Escopo |
|
|
190
|
+
|---|---|
|
|
191
|
+
| **E2E Funcional** | Um script por CT — valida contrato e regras de negócio |
|
|
192
|
+
| **Teste de Carga (Load)** | Scripts de throughput por estágio de RPS — apenas CTs de sucesso |
|
|
193
|
+
| **Chaos Engineering** | Scripts de resiliência sob falha — apenas CTs de sucesso |
|
|
194
|
+
|
|
195
|
+
Registre os tipos selecionados — eles controlam quais seções e arquivos são gerados.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
#### Pergunta C — Especificação técnica detalhada
|
|
200
|
+
|
|
201
|
+
**AskUserQuestion** (open-ended):
|
|
202
|
+
> "Descreva tecnicamente o que deseja testar. Preencha o JSON abaixo com o que souber — quanto mais detalhe, mais precisa a especificação gerada."
|
|
203
|
+
|
|
204
|
+
Apresente o template:
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"request": {
|
|
209
|
+
"httpMethod": "POST",
|
|
210
|
+
"path": "/resource",
|
|
211
|
+
"headers": {},
|
|
212
|
+
"requestBody": {}
|
|
213
|
+
},
|
|
214
|
+
"rules": [
|
|
215
|
+
{ "description": "Regra de negócio ou comportamento esperado" },
|
|
216
|
+
{ "classes": { "entity": ["field1", "field2"] }, "description": "Campos que devem ser persistidos" },
|
|
217
|
+
{ "sequenceDescription": "Sequência de operações que o sistema deve executar" }
|
|
218
|
+
],
|
|
219
|
+
"response": {
|
|
220
|
+
"status": 201,
|
|
221
|
+
"headers": {
|
|
222
|
+
"Location": "/resource/{id}"
|
|
223
|
+
},
|
|
224
|
+
"body": {}
|
|
225
|
+
},
|
|
226
|
+
"database": {
|
|
227
|
+
"table": "table_name",
|
|
228
|
+
"assertedFields": ["field1", "field2"]
|
|
229
|
+
},
|
|
230
|
+
"loadProfile": {
|
|
231
|
+
"stages": [
|
|
232
|
+
{ "rps": 100, "duration": "5m" },
|
|
233
|
+
{ "rps": 1000, "duration": "5m" }
|
|
234
|
+
]
|
|
235
|
+
},
|
|
236
|
+
"chaosScenarios": [
|
|
237
|
+
{ "type": "shutdown-pods", "description": "Desliga pods durante o teste" }
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### 5. Análise profunda — extrair detalhes técnicos
|
|
245
|
+
|
|
246
|
+
Com `tests.md` + respostas do questionário, analise **antes de gerar qualquer arquivo**:
|
|
247
|
+
|
|
248
|
+
**Do `request`:**
|
|
249
|
+
- Quais campos são obrigatórios vs opcionais?
|
|
250
|
+
- Há tipos especiais: UUID, enum, nested object, null explícito?
|
|
251
|
+
- Há headers necessários: Authorization, Content-Type, X-Correlation-Id?
|
|
252
|
+
|
|
253
|
+
**Das `rules`:**
|
|
254
|
+
- Quais rules implicam asserções de banco (persistência, lock, update parcial)?
|
|
255
|
+
- Há `sequenceDescription`? → gere critério de ordem de operações nos CTs correspondentes
|
|
256
|
+
- Há campos cujo valor no banco difere do enviado na request (ex: campo calculado)?
|
|
257
|
+
|
|
258
|
+
**Da `response`:**
|
|
259
|
+
- Há `Location` header? → todo script de sucesso deve extrair o ID e usá-lo em asserções
|
|
260
|
+
- O body tem campos derivados (código gerado, timestamps)? → defina como validá-los
|
|
261
|
+
- Qual é o código HTTP exato para cada CT de rejeição?
|
|
262
|
+
|
|
263
|
+
**Do `loadProfile`:**
|
|
264
|
+
- VUs estimados por estágio: `VUs ≈ RPS × latência_média_em_segundos` (use 0.2s como default se desconhecida)
|
|
265
|
+
- Há necessidade de warmup antes do platô?
|
|
266
|
+
|
|
267
|
+
**Do `chaosScenarios`:**
|
|
268
|
+
- O mecanismo de caos requer kubectl, toxiproxy ou outra ferramenta?
|
|
269
|
+
|
|
270
|
+
Se qualquer ponto estiver ambíguo, **faça uma pergunta de esclarecimento** antes de prosseguir.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
### 6. Verificar existência de `testspec/{feature-name}/`
|
|
275
|
+
|
|
276
|
+
Verifique se `./testspec/{feature-name}/` já existe no diretório atual.
|
|
277
|
+
|
|
278
|
+
Se existir, pergunte via **AskUserQuestion** (single select):
|
|
279
|
+
- **Sobrescrever** — apaga e recria os arquivos
|
|
280
|
+
- **Cancelar** — encerra sem alterações
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
### 7. Criar estrutura de diretórios
|
|
285
|
+
|
|
286
|
+
Crie **todos** os diretórios abaixo relativos ao diretório atual (`.`):
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
./testspec/{feature-name}/
|
|
290
|
+
|
|
291
|
+
./src/test/features/{feature-name}/e2e/
|
|
292
|
+
./src/test/features/{feature-name}/load/ ← somente se Load selecionado
|
|
293
|
+
./src/test/features/{feature-name}/chaos-engineering/ ← somente se Chaos selecionado
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
### 8. Gerar `./testspec/{feature-name}/spec.qa.md`
|
|
299
|
+
|
|
300
|
+
Siga o **Formato spec.qa.md** abaixo.
|
|
301
|
+
|
|
302
|
+
### 9. Gerar `./testspec/{feature-name}/tasks.qa.md`
|
|
303
|
+
|
|
304
|
+
Siga o **Formato tasks.qa.md** abaixo.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
### 10. Exibir confirmação final
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
Especificação QA gerada para '{feature-name}':
|
|
312
|
+
|
|
313
|
+
Ferramenta: {K6 | Gatling | Outro}
|
|
314
|
+
Testes: {E2E} {Load} {Chaos}
|
|
315
|
+
|
|
316
|
+
Artefatos criados em ./testspec/{feature-name}/
|
|
317
|
+
spec.qa.md
|
|
318
|
+
tasks.qa.md
|
|
319
|
+
|
|
320
|
+
Estrutura de pastas criada em ./src/test/features/{feature-name}/
|
|
321
|
+
e2e/ ({N} scripts mapeados)
|
|
322
|
+
load/ ({N} scripts mapeados) ← omitir se não selecionado
|
|
323
|
+
chaos-engineering/ ({N} scripts) ← omitir se não selecionado
|
|
324
|
+
|
|
325
|
+
Próximo passo: /testspec-apply-qa para implementar os scripts.
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Formato spec.qa.md
|
|
331
|
+
|
|
332
|
+
```markdown
|
|
333
|
+
# Tests Spec — {Nome Legível da Feature}
|
|
334
|
+
|
|
335
|
+
> Especificação técnica dos scripts de teste derivada do tests.md + questionário QA.
|
|
336
|
+
> Ferramenta: {K6 | Gatling | Outro}
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Contexto
|
|
341
|
+
|
|
342
|
+
{Resumo da feature: protocolo de entrada, operação, regras de negócio relevantes para os testes. 3-5 linhas.}
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Contrato Técnico
|
|
347
|
+
|
|
348
|
+
### Request
|
|
349
|
+
|
|
350
|
+
```
|
|
351
|
+
{HTTP_METHOD} {path}
|
|
352
|
+
Content-Type: application/json
|
|
353
|
+
{outros headers relevantes com exemplo de valor}
|
|
354
|
+
|
|
355
|
+
Body:
|
|
356
|
+
{
|
|
357
|
+
"{campo}": <Tipo>, -- {obrigatório|opcional} — {descrição}
|
|
358
|
+
...
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Response (Sucesso)
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
HTTP Status: {código}
|
|
366
|
+
Location: {path}/{id} ← incluir somente se presente
|
|
367
|
+
|
|
368
|
+
Body:
|
|
369
|
+
{
|
|
370
|
+
"{campo}": <Tipo>, -- {descrição}
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Regras de Negócio para Testes
|
|
375
|
+
|
|
376
|
+
{Derivadas das `rules` do questionário e dos critérios do tests.md}
|
|
377
|
+
- {regra 1}
|
|
378
|
+
- {regra 2}
|
|
379
|
+
- {regra N}
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Cobertura de Testes
|
|
384
|
+
|
|
385
|
+
### E2E
|
|
386
|
+
|
|
387
|
+
| CT | Arquivo | Cenário | Tipo |
|
|
388
|
+
|-------|-----------------------------------------------|---------------------|----------|
|
|
389
|
+
| CT-01 | {tool}-e2e-{acao}-{cenario}{ext} | {nome do cenário} | Sucesso |
|
|
390
|
+
| CT-NN | {tool}-e2e-{acao}-{cenario}{ext} | {nome do cenário} | Rejeição |
|
|
391
|
+
|
|
392
|
+
### Load ← incluir somente se Load selecionado
|
|
393
|
+
|
|
394
|
+
| Cenário | Arquivo | VUs est. | Duração | RPS alvo |
|
|
395
|
+
|---------|------------------------------------------------------|----------|---------|----------|
|
|
396
|
+
| CT-01 | {tool}-load-{acao}-{cenario}-100-rps-5min{ext} | ~20 | 5min | 100 |
|
|
397
|
+
| CT-01 | {tool}-load-{acao}-{cenario}-1000-rps-5min{ext} | ~200 | 5min | 1000 |
|
|
398
|
+
|
|
399
|
+
### Chaos Engineering ← incluir somente se Chaos selecionado
|
|
400
|
+
|
|
401
|
+
| Cenário | Arquivo | Tipo de caos | Mecanismo |
|
|
402
|
+
|---------|------------------------------------------------------|---------------|---------------|
|
|
403
|
+
| CT-01 | {tool}-dr-{acao}-{cenario}-shutdown-pods{ext} | Pod shutdown | kubectl delete|
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Estrutura de Pastas
|
|
408
|
+
|
|
409
|
+
```
|
|
410
|
+
src/test/features/{feature-name}/
|
|
411
|
+
e2e/
|
|
412
|
+
{tool}-e2e-{acao}-{cenario}{ext}
|
|
413
|
+
{tool}-e2e-{acao}-{cenario}.md ← run plan gerado pelo /testspec-apply-qa
|
|
414
|
+
load/ ← somente se Load selecionado
|
|
415
|
+
{tool}-load-{acao}-{cenario}-{rps}-rps-{duracao}{ext}
|
|
416
|
+
{tool}-load-{acao}-{cenario}-{rps}-rps-{duracao}.md ← run plan
|
|
417
|
+
chaos-engineering/ ← somente se Chaos selecionado
|
|
418
|
+
{tool}-dr-{acao}-{cenario}-{tipo-caos}{ext}
|
|
419
|
+
{tool}-dr-{acao}-{cenario}-{tipo-caos}.md ← run plan
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## Convenção de Nomenclatura
|
|
423
|
+
|
|
424
|
+
| Tipo | Padrão |
|
|
425
|
+
|-------|-----------------------------------------------------|
|
|
426
|
+
| E2E | `{tool}-e2e-{acao}-{cenario}{ext}` |
|
|
427
|
+
| Load | `{tool}-load-{acao}-{cenario}-{rps}-rps-{dur}{ext}` |
|
|
428
|
+
| Chaos | `{tool}-dr-{acao}-{cenario}-{tipo-caos}{ext}` |
|
|
429
|
+
|
|
430
|
+
Onde:
|
|
431
|
+
- `{acao}`: verbo da operação em kebab-case (ex: `create-order`, `consume-message`)
|
|
432
|
+
- `{cenario}`: resultado esperado em kebab-case (ex: `success`, `bad-request-zero-value`)
|
|
433
|
+
- `{rps}`: número inteiro de requisições por segundo alvo
|
|
434
|
+
- `{dur}`: duração do estágio (ex: `5min`, `10min`)
|
|
435
|
+
- `{tipo-caos}`: tipo de falha em kebab-case (ex: `shutdown-pods`, `network-latency`)
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Casos de Teste — Mapeamento Detalhado
|
|
440
|
+
|
|
441
|
+
### CT-01 — {Nome do Cenário}
|
|
442
|
+
|
|
443
|
+
**Arquivo E2E:** `src/test/features/{feature-name}/e2e/{tool}-e2e-{...}{ext}`
|
|
444
|
+
**Tipo:** Sucesso
|
|
445
|
+
|
|
446
|
+
**Entrada:**
|
|
447
|
+
```json
|
|
448
|
+
{payload completo extraído do tests.md e do questionário, com valores de exemplo reais}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**Critérios de Aceite para o script:**
|
|
452
|
+
- Verificar status HTTP {código}
|
|
453
|
+
- {Se Location header presente}: Extrair `{id}` do header `Location` e usar em asserções subsequentes
|
|
454
|
+
- Verificar campo `{campo}` no body de resposta igual a `{valor}`
|
|
455
|
+
- {Se persistência}: Consultar tabela `{tabela}` e verificar `{campo}` = `{valor}` após a operação
|
|
456
|
+
- {Se sequenceDescription}: Verificar que `{operação A}` ocorre antes de `{operação B}`
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
### CT-NN — {Nome do Cenário}
|
|
461
|
+
|
|
462
|
+
**Arquivo E2E:** `src/test/features/{feature-name}/e2e/{tool}-e2e-{...}{ext}`
|
|
463
|
+
**Tipo:** Rejeição
|
|
464
|
+
|
|
465
|
+
**Entrada:**
|
|
466
|
+
```json
|
|
467
|
+
{payload inválido com o campo problemático destacado}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**Critérios de Aceite para o script:**
|
|
471
|
+
- Verificar status HTTP {código de erro — ex: 400, 422}
|
|
472
|
+
- Verificar que COUNT na tabela `{tabela}` permanece igual ao valor anterior à requisição
|
|
473
|
+
- {Se body de erro padronizado}: Verificar estrutura do body de erro
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Formato tasks.qa.md
|
|
479
|
+
|
|
480
|
+
```markdown
|
|
481
|
+
# Tasks — QA {Nome Legível da Feature}
|
|
482
|
+
|
|
483
|
+
> Tarefas de implementação derivadas do spec.qa.md.
|
|
484
|
+
> Ferramenta: {K6 | Gatling | Outro}
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## 1. E2E Tests
|
|
489
|
+
|
|
490
|
+
- [ ] 1.1 Criar `src/test/features/{feature-name}/e2e/{tool}-e2e-{...}{ext}` — CT-01: {nome}
|
|
491
|
+
- [ ] 1.2 Criar `src/test/features/{feature-name}/e2e/{tool}-e2e-{...}.md` — run plan CT-01
|
|
492
|
+
- [ ] 1.N Criar `src/test/features/{feature-name}/e2e/{tool}-e2e-{...}{ext}` — CT-NN: {nome}
|
|
493
|
+
- [ ] 1.N+1 Criar `src/test/features/{feature-name}/e2e/{tool}-e2e-{...}.md` — run plan CT-NN
|
|
494
|
+
|
|
495
|
+
## 2. Load Tests ← incluir somente se Load selecionado
|
|
496
|
+
|
|
497
|
+
- [ ] 2.1 Criar `src/test/features/{feature-name}/load/{tool}-load-{...}-100-rps-5min{ext}` — CT-01, 100 RPS
|
|
498
|
+
- [ ] 2.2 Criar `src/test/features/{feature-name}/load/{tool}-load-{...}-100-rps-5min.md` — run plan
|
|
499
|
+
- [ ] 2.3 Criar `src/test/features/{feature-name}/load/{tool}-load-{...}-1000-rps-5min{ext}` — CT-01, 1000 RPS
|
|
500
|
+
- [ ] 2.4 Criar `src/test/features/{feature-name}/load/{tool}-load-{...}-1000-rps-5min.md` — run plan
|
|
501
|
+
|
|
502
|
+
## 3. Chaos Engineering ← incluir somente se Chaos selecionado
|
|
503
|
+
|
|
504
|
+
- [ ] 3.1 Criar `src/test/features/{feature-name}/chaos-engineering/{tool}-dr-{...}-shutdown-pods{ext}` — CT-01, pod failure
|
|
505
|
+
- [ ] 3.2 Criar `src/test/features/{feature-name}/chaos-engineering/{tool}-dr-{...}-shutdown-pods.md` — run plan
|
|
506
|
+
|
|
507
|
+
## 4. Verificação
|
|
508
|
+
|
|
509
|
+
- [ ] 4.1 Validar sintaxe de todos os scripts E2E (dry-run ou lint da ferramenta escolhida)
|
|
510
|
+
- [ ] 4.2 Revisar thresholds de load com SRE/produto antes de executar
|
|
511
|
+
- [ ] 4.3 Confirmar mecanismo de caos disponível no ambiente (kubectl, toxiproxy, etc.)
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## Guardrails
|
|
517
|
+
|
|
518
|
+
- **Nunca peça ao usuário para criar arquivos ou diretórios** — se `./testspec/`, `instructions.md` ou `current-feature.md` não existirem, crie-os imediatamente com conteúdo padrão e prossiga
|
|
519
|
+
- **`instructions.md` criado automaticamente** com valores opinativos sensatos (`app_repo_local_path: ../development-flow-sdd-sdt`, k6 como ferramenta padrão, thresholds default)
|
|
520
|
+
- **`instructions.md` tem precedência** sobre qualquer default da skill após criado — se o usuário personalizou, siga o arquivo
|
|
521
|
+
- **`current-feature.md` elimina a pergunta de seleção** — se contiver um nome de feature, use-o diretamente sem perguntar ao usuário
|
|
522
|
+
- **Diretório de saída:** sempre `./testspec/` e `./src/test/features/` relativos ao diretório atual — nunca use paths absolutos ou hardcoded
|
|
523
|
+
- **GitHub MCP é a única fonte do app repo** — nunca use caminhos relativos ou locais (`../`) para acessar o repositório do app
|
|
524
|
+
- **`app_repo` em `instructions.md`** define o `owner/repo` usado em todas as chamadas GitHub MCP — sempre leia dali, nunca hardcode
|
|
525
|
+
- **Seleção de feature obrigatória quando `current-feature.md` está vazio:** sempre listar features do GitHub MCP e perguntar — nunca assumir
|
|
526
|
+
- **Questionário obrigatório:** não gere nenhum arquivo antes de concluir as Perguntas A, B e C (spec técnica)
|
|
527
|
+
- **Nomenclatura:** sempre kebab-case, sem acentos, sem espaços, sem caracteres especiais
|
|
528
|
+
- **`tasks.qa.md`:** usa exclusivamente `- [ ]` — nunca bullets simples
|
|
529
|
+
- **Load e Chaos:** SOMENTE para CTs de sucesso (happy path) — CTs de rejeição ficam apenas em E2E
|
|
530
|
+
- **Seções condicionais:** omitir Load e Chaos inteiramente se não foram selecionados em Pergunta B
|
|
531
|
+
- **Protocolo Kafka:** se o protocolo for Kafka (não HTTP), omitir seções HTTP, adaptar `{acao}` para `consume-{recurso}`, omitir `Location` header
|
|
532
|
+
- **Location header:** se presente na resposta, SEMPRE incluir instrução de extração do ID nos critérios de aceite do CT de sucesso
|
|
533
|
+
- **sequenceDescription:** se presente nas rules, SEMPRE gerar critério de ordem de operações no CT correspondente
|
|
534
|
+
- **Próximo passo:** sempre referenciar `/testspec-apply-qa`
|
|
535
|
+
- **Run plans:** sempre gerados pelo `/testspec-apply-qa` junto com cada script — não pergunte ao usuário, não condicione
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{CT_ID}} — {{TITLE}}
|
|
3
|
+
* Feature: {{FEATURE}}
|
|
4
|
+
* Auto-generated by testspec. Fill in arrange/act/assert.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
describe('{{CT_ID}} — {{TITLE}}', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
// arrange: set up state from spec precondition
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should {{TITLE}}', async () => {
|
|
13
|
+
// Input: (from CT input field)
|
|
14
|
+
// Expected output: (from CT expected output field)
|
|
15
|
+
expect(true).toBe(true); // replace with real assertion
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{CT_ID}} — {{TITLE}}
|
|
3
|
+
* Feature: {{FEATURE}}
|
|
4
|
+
* Auto-generated by testspec. Fill in arrange/act/assert.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import org.junit.jupiter.api.BeforeEach;
|
|
8
|
+
import org.junit.jupiter.api.DisplayName;
|
|
9
|
+
import org.junit.jupiter.api.Test;
|
|
10
|
+
import static org.assertj.core.api.Assertions.assertThat;
|
|
11
|
+
|
|
12
|
+
@DisplayName("{{CT_ID}} — {{TITLE}}")
|
|
13
|
+
class {{CT_ID_CAMEL}}Test {
|
|
14
|
+
|
|
15
|
+
@BeforeEach
|
|
16
|
+
void setUp() {
|
|
17
|
+
// arrange: set up state from spec precondition
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Test
|
|
21
|
+
@DisplayName("should {{TITLE}}")
|
|
22
|
+
void should{{CT_ID_CAMEL}}() {
|
|
23
|
+
// Input: (from CT input field)
|
|
24
|
+
// Expected output: (from CT expected output field)
|
|
25
|
+
assertThat(true).isTrue(); // replace with real assertion
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
{{CT_ID}} — {{TITLE}}
|
|
3
|
+
Feature: {{FEATURE}}
|
|
4
|
+
Auto-generated by testspec. Fill in arrange/act/assert.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Test{{CT_ID}}:
|
|
11
|
+
def setup_method(self):
|
|
12
|
+
# arrange: set up state from spec precondition
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
def test_{{CT_ID_LOWER}}(self):
|
|
16
|
+
# Input: (from CT input field)
|
|
17
|
+
# Expected output: (from CT expected output field)
|
|
18
|
+
assert True # replace with real assertion
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test stub — {{FEATURE}} (change: {{CHANGE}})
|
|
3
|
+
* Stack: Node.js + PostgreSQL + Kafka (Testcontainers)
|
|
4
|
+
* Auto-generated by testspec. Fill in HTTP calls and DB/Kafka assertions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
8
|
+
import { PostgreSqlContainer } from '@testcontainers/postgresql';
|
|
9
|
+
import { KafkaContainer } from '@testcontainers/kafka';
|
|
10
|
+
|
|
11
|
+
describe('{{FEATURE}} — integration', () => {
|
|
12
|
+
let pgContainer, kafkaContainer;
|
|
13
|
+
let connectionString, brokerUrl;
|
|
14
|
+
|
|
15
|
+
beforeAll(async () => {
|
|
16
|
+
[pgContainer, kafkaContainer] = await Promise.all([
|
|
17
|
+
new PostgreSqlContainer('postgres:16-alpine').start(),
|
|
18
|
+
new KafkaContainer('confluentinc/cp-kafka:7.4.0').start(),
|
|
19
|
+
]);
|
|
20
|
+
connectionString = pgContainer.getConnectionUri();
|
|
21
|
+
brokerUrl = `${kafkaContainer.getHost()}:${kafkaContainer.getMappedPort(9093)}`;
|
|
22
|
+
// run migrations here if needed
|
|
23
|
+
}, 90_000);
|
|
24
|
+
|
|
25
|
+
afterAll(async () => {
|
|
26
|
+
await Promise.all([pgContainer.stop(), kafkaContainer.stop()]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
{{SCENARIOS}}
|
|
30
|
+
|
|
31
|
+
it('CT-placeholder — replace with real CT', async () => {
|
|
32
|
+
// Input: (from tests.md CT input field)
|
|
33
|
+
// Expected output: (from tests.md CT expected output field)
|
|
34
|
+
// DB validation: (from tests.md CT DB validation field)
|
|
35
|
+
expect(connectionString).toBeTruthy();
|
|
36
|
+
expect(brokerUrl).toBeTruthy();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test stub — {{FEATURE}} (change: {{CHANGE}})
|
|
3
|
+
* Stack: Node.js + PostgreSQL (Testcontainers)
|
|
4
|
+
* Auto-generated by testspec. Fill in HTTP calls and DB assertions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
8
|
+
import { PostgreSqlContainer } from '@testcontainers/postgresql';
|
|
9
|
+
|
|
10
|
+
describe('{{FEATURE}} — integration', () => {
|
|
11
|
+
let container;
|
|
12
|
+
let connectionString;
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
container = await new PostgreSqlContainer('postgres:16-alpine').start();
|
|
16
|
+
connectionString = container.getConnectionUri();
|
|
17
|
+
// run migrations here if needed
|
|
18
|
+
}, 60_000);
|
|
19
|
+
|
|
20
|
+
afterAll(async () => {
|
|
21
|
+
await container.stop();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
{{SCENARIOS}}
|
|
25
|
+
|
|
26
|
+
it('CT-placeholder — replace with real CT', async () => {
|
|
27
|
+
// Input: (from tests.md CT input field)
|
|
28
|
+
// Expected output: (from tests.md CT expected output field)
|
|
29
|
+
// DB validation: (from tests.md CT DB validation field)
|
|
30
|
+
expect(connectionString).toBeTruthy();
|
|
31
|
+
});
|
|
32
|
+
});
|