bddgenx 0.1.10 → 0.1.12
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.
- checksums.yaml +4 -4
- data/lib/bddgenx/backup.rb +11 -9
- data/lib/bddgenx/cli.rb +28 -26
- data/lib/bddgenx/generator.rb +119 -118
- data/lib/bddgenx/steps_generator.rb +128 -126
- data/lib/bddgenx/tracer.rb +22 -20
- data/lib/bddgenx/validator.rb +28 -26
- data/lib/bddgenx/version.rb +1 -1
- data/lib/bddgenx.rb +7 -7
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67645045af3cc9180b3e34f3743bd21fe90d8743a56febed112b1e1d787b5b85
|
4
|
+
data.tar.gz: dc0368773bc04a891a03085a99251a98223f840d5a75b0c7bd698575ee97e5ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8fc504876ececf91dbe5bcc2a48ba2596c9241b602aed83c813bacc0c4bc138a4257fa078483d9abbdb60f7584eb7cf0e7437851726a538c43ad9884ef1a317
|
7
|
+
data.tar.gz: a23d58672473d4b8f0809199f4da232abc798e57bd95632d93af6f73a78c40d8f2bb44c9546c61ac912bbb9dd30ce3c3d51d0de20c628870590979c1555b92a1
|
data/lib/bddgenx/backup.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require 'time'
|
3
3
|
|
4
|
-
module
|
5
|
-
|
6
|
-
|
4
|
+
module Bddgenx
|
5
|
+
class Backup
|
6
|
+
def self.salvar_versao_antiga(caminho)
|
7
|
+
return unless File.exist?(caminho)
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
FileUtils.mkdir_p("backup")
|
10
|
+
base = File.basename(caminho, ".feature")
|
11
|
+
timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
|
12
|
+
destino = "backup/#{base}_#{timestamp}.feature"
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
FileUtils.cp(caminho, destino)
|
15
|
+
puts "📦 Backup criado: #{destino}"
|
16
|
+
end
|
15
17
|
end
|
16
18
|
end
|
data/lib/bddgenx/cli.rb
CHANGED
@@ -1,34 +1,36 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
1
|
+
module Bddgenx
|
2
|
+
class Cli
|
3
|
+
def self.selecionar_arquivos_txt(diretorio)
|
4
|
+
arquivos = Dir.glob("#{diretorio}/*.txt")
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
arquivos
|
6
|
+
if arquivos.empty?
|
7
|
+
puts "❌ Nenhum arquivo .txt encontrado no diretório '#{diretorio}'"
|
8
|
+
exit
|
9
|
+
end
|
11
10
|
|
12
|
-
|
13
|
-
arquivos.each_with_index do |arquivo, i|
|
14
|
-
puts " #{i + 1}. #{File.basename(arquivo)}"
|
15
|
-
end
|
11
|
+
arquivos
|
16
12
|
|
17
|
-
|
18
|
-
|
13
|
+
puts "📂 Arquivos disponíveis em '#{diretorio}':"
|
14
|
+
arquivos.each_with_index do |arquivo, i|
|
15
|
+
puts " #{i + 1}. #{File.basename(arquivo)}"
|
16
|
+
end
|
19
17
|
|
20
|
-
|
21
|
-
|
22
|
-
else
|
23
|
-
indices = entrada.split(',').map { |n| n.strip.to_i - 1 }
|
24
|
-
indices.map { |i| arquivos[i] }.compact
|
25
|
-
end
|
18
|
+
print "\nDigite os números dos arquivos que deseja processar (ex: 1,2,3 ou 'todos'): "
|
19
|
+
entrada = gets.chomp
|
26
20
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
21
|
+
selecionados = if entrada.downcase == 'todos'
|
22
|
+
arquivos
|
23
|
+
else
|
24
|
+
indices = entrada.split(',').map { |n| n.strip.to_i - 1 }
|
25
|
+
indices.map { |i| arquivos[i] }.compact
|
26
|
+
end
|
31
27
|
|
32
|
-
|
28
|
+
if selecionados.empty?
|
29
|
+
puts "❌ Nenhum arquivo válido selecionado."
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
|
33
|
+
selecionados
|
34
|
+
end
|
33
35
|
end
|
34
36
|
end
|
data/lib/bddgenx/generator.rb
CHANGED
@@ -1,150 +1,151 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
TIPOS_CENARIO = %w[
|
16
|
-
SUCCESS FAILURE ERROR EXCEPTION
|
17
|
-
VALIDATION PERMISSION EDGE_CASE PERFORMANCE
|
18
|
-
]
|
19
|
-
|
20
|
-
|
21
|
-
def self.gerar_feature(historia)
|
22
|
-
idioma = historia[:idioma] || 'pt'
|
23
|
-
|
24
|
-
# Define os conectores de acordo com o idioma
|
25
|
-
palavras = {
|
26
|
-
contexto: idioma == 'en' ? 'Background' : 'Contexto',
|
27
|
-
cenario: idioma == 'en' ? 'Scenario' : 'Cenário',
|
28
|
-
esquema: idioma == 'en' ? 'Scenario Outline' : 'Esquema do Cenário',
|
29
|
-
exemplos: idioma == 'en' ? 'Examples' : 'Exemplos',
|
30
|
-
regra: idioma == 'en' ? 'Rule' : 'Regra'
|
3
|
+
module Bddgenx
|
4
|
+
class Generator
|
5
|
+
TIPOS_ISTQB = {
|
6
|
+
"SUCCESS" => "Teste Positivo",
|
7
|
+
"FAILURE" => "Teste Negativo",
|
8
|
+
"ERROR" => "Teste de Erro",
|
9
|
+
"EXCEPTION" => "Teste de Exceção",
|
10
|
+
"VALIDATION" => "Teste de Validação",
|
11
|
+
"PERMISSION" => "Teste de Permissão",
|
12
|
+
"EDGE_CASE" => "Teste de Limite",
|
13
|
+
"PERFORMANCE" => "Teste de Desempenho"
|
31
14
|
}
|
32
15
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
16
|
+
TIPOS_CENARIO = %w[
|
17
|
+
SUCCESS FAILURE ERROR EXCEPTION
|
18
|
+
VALIDATION PERMISSION EDGE_CASE PERFORMANCE
|
19
|
+
]
|
20
|
+
|
21
|
+
|
22
|
+
def self.gerar_feature(historia)
|
23
|
+
idioma = historia[:idioma] || 'pt'
|
24
|
+
|
25
|
+
# Define os conectores de acordo com o idioma
|
26
|
+
palavras = {
|
27
|
+
contexto: idioma == 'en' ? 'Background' : 'Contexto',
|
28
|
+
cenario: idioma == 'en' ? 'Scenario' : 'Cenário',
|
29
|
+
esquema: idioma == 'en' ? 'Scenario Outline' : 'Esquema do Cenário',
|
30
|
+
exemplos: idioma == 'en' ? 'Examples' : 'Exemplos',
|
31
|
+
regra: idioma == 'en' ? 'Rule' : 'Regra'
|
32
|
+
}
|
33
|
+
|
34
|
+
nome_base = historia[:quero].gsub(/[^a-zA-Z0-9]/, '_').downcase
|
35
|
+
caminho = "features/#{nome_base}.feature"
|
36
|
+
|
37
|
+
conteudo = <<~HEADER
|
38
|
+
# language: #{idioma}
|
39
|
+
Funcionalidade: #{historia[:quero].sub(/^Quero/, '').strip}
|
40
|
+
|
41
|
+
#{historia[:como]}
|
42
|
+
#{historia[:quero]}
|
43
|
+
#{historia[:para]}
|
44
|
+
|
45
|
+
HEADER
|
46
|
+
|
47
|
+
# Regras
|
48
|
+
if historia[:regras]&.any?
|
49
|
+
conteudo += " #{palavras[:regra]}: #{historia[:regras].first}\n"
|
50
|
+
historia[:regras][1..].each do |linha|
|
51
|
+
conteudo += " #{linha}\n"
|
52
|
+
end
|
53
|
+
conteudo += "\n"
|
54
|
+
end
|
43
55
|
|
44
|
-
HEADER
|
45
56
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
conteudo += "
|
57
|
+
# Contexto
|
58
|
+
if historia[:blocos]["CONTEXT"]&.any?
|
59
|
+
conteudo += " #{palavras[:contexto]}:\n"
|
60
|
+
historia[:blocos]["CONTEXT"].each { |p| conteudo += " #{p}\n" }
|
61
|
+
conteudo += "\n"
|
51
62
|
end
|
52
|
-
conteudo += "\n"
|
53
|
-
end
|
54
63
|
|
64
|
+
# Cenários
|
65
|
+
TIPOS_CENARIO.each do |tipo|
|
66
|
+
passos = historia[:blocos][tipo]
|
67
|
+
passos = passos&.reject { |l| l.strip.empty? } || []
|
68
|
+
next if passos.empty?
|
55
69
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
70
|
+
# Ignora geração duplicada se for um SUCCESS parametrizado com EXAMPLES
|
71
|
+
if tipo == "SUCCESS" && historia[:blocos]["EXAMPLES"]&.any?
|
72
|
+
possui_parametros = passos.any? { |p| p.include?('<') }
|
73
|
+
next if possui_parametros
|
74
|
+
end
|
62
75
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
next if passos.empty?
|
76
|
+
nome_teste = TIPOS_ISTQB[tipo] || palavras[:cenario]
|
77
|
+
contexto = passos.first&.gsub(/^(Dado que|Given|Quando|When|Então|Then|E|And)/, '')&.strip || "Condição"
|
78
|
+
resultado = passos.last&.gsub(/^(Então|Then|E|And)/, '')&.strip || "Resultado"
|
79
|
+
nome_cenario = "#{nome_teste} - #{contexto} - #{resultado}"
|
68
80
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
81
|
+
conteudo += " @#{tipo.downcase}\n"
|
82
|
+
conteudo += " #{palavras[:cenario]}: #{nome_cenario}\n"
|
83
|
+
passos.each { |p| conteudo += " #{p}\n" }
|
84
|
+
conteudo += "\n"
|
73
85
|
end
|
74
86
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
87
|
+
# Esquema do Cenário com Exemplos
|
88
|
+
if historia[:blocos]["EXAMPLES"]&.any?
|
89
|
+
exemplo_bruto = historia[:blocos]["EXAMPLES"]
|
90
|
+
grupos = dividir_examples(exemplo_bruto)
|
79
91
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
conteudo += "\n"
|
84
|
-
end
|
92
|
+
grupos.each_with_index do |tabela, i|
|
93
|
+
cabecalho = tabela.first.gsub('|', '').split.map(&:strip)
|
94
|
+
linhas = tabela[1..].map { |linha| linha.split('|').reject(&:empty?).map(&:strip) }
|
85
95
|
|
86
|
-
|
87
|
-
if historia[:blocos]["EXAMPLES"]&.any?
|
88
|
-
exemplo_bruto = historia[:blocos]["EXAMPLES"]
|
89
|
-
grupos = dividir_examples(exemplo_bruto)
|
96
|
+
exemplos = { cabecalho: cabecalho, linhas: linhas }
|
90
97
|
|
91
|
-
|
92
|
-
|
93
|
-
|
98
|
+
passos_outline = historia[:blocos]["SUCCESS"].select do |linha|
|
99
|
+
cabecalho.any? { |coluna| linha.include?("<#{coluna}>") }
|
100
|
+
end
|
94
101
|
|
95
|
-
|
102
|
+
next if passos_outline.empty?
|
96
103
|
|
97
|
-
|
98
|
-
|
99
|
-
end
|
104
|
+
conteudo += "\n"
|
105
|
+
conteudo += idioma == 'en' ? " Scenario Outline: Example #{i + 1}\n" : " Esquema do Cenário: Exemplo #{i + 1}\n"
|
100
106
|
|
101
|
-
|
107
|
+
passos_outline.each do |passo|
|
108
|
+
conteudo += " #{passo}\n"
|
109
|
+
end
|
102
110
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
conteudo += " #{passo}\n"
|
111
|
+
conteudo += "\n"
|
112
|
+
conteudo += idioma == 'en' ? " Examples:\n" : " Exemplos:\n"
|
113
|
+
conteudo += " #{tabela.first}\n"
|
114
|
+
tabela[1..].each { |linha| conteudo += " #{linha}\n" }
|
108
115
|
end
|
109
|
-
|
110
|
-
conteudo += "\n"
|
111
|
-
conteudo += idioma == 'en' ? " Examples:\n" : " Exemplos:\n"
|
112
|
-
conteudo += " #{tabela.first}\n"
|
113
|
-
tabela[1..].each { |linha| conteudo += " #{linha}\n" }
|
114
116
|
end
|
117
|
+
|
118
|
+
[caminho, conteudo]
|
115
119
|
end
|
116
120
|
|
117
|
-
|
118
|
-
|
121
|
+
# Salva o arquivo .feature gerado
|
122
|
+
# Retorna true se o arquivo foi salvo com sucesso, false caso contrário
|
123
|
+
def self.salvar_feature(caminho, conteudo)
|
124
|
+
if conteudo.strip.empty?
|
125
|
+
puts "⚠️ Nenhum conteúdo gerado para: #{caminho} (ignorado)"
|
126
|
+
return false
|
127
|
+
end
|
119
128
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
if conteudo.strip.empty?
|
124
|
-
puts "⚠️ Nenhum conteúdo gerado para: #{caminho} (ignorado)"
|
125
|
-
return false
|
129
|
+
File.write(caminho, conteudo)
|
130
|
+
puts "✅ Arquivo .feature gerado: #{caminho}"
|
131
|
+
true
|
126
132
|
end
|
127
133
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
end
|
132
|
-
|
133
|
-
def self.dividir_examples(tabela_bruta)
|
134
|
-
grupos = []
|
135
|
-
grupo_atual = []
|
134
|
+
def self.dividir_examples(tabela_bruta)
|
135
|
+
grupos = []
|
136
|
+
grupo_atual = []
|
136
137
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
138
|
+
tabela_bruta.each do |linha|
|
139
|
+
if linha.strip =~ /^\|\s*[\w\s]+\|/ && grupo_atual.any? && linha.strip == linha.strip.squeeze(" ")
|
140
|
+
grupos << grupo_atual
|
141
|
+
grupo_atual = [linha]
|
142
|
+
else
|
143
|
+
grupo_atual << linha
|
144
|
+
end
|
143
145
|
end
|
144
|
-
end
|
145
146
|
|
146
|
-
|
147
|
-
|
147
|
+
grupos << grupo_atual unless grupo_atual.empty?
|
148
|
+
grupos
|
149
|
+
end
|
148
150
|
end
|
149
|
-
|
150
151
|
end
|
@@ -1,162 +1,164 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require_relative 'utils/verificador'
|
3
3
|
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
4
|
+
module Bddgenx
|
5
|
+
class StepsGenerator
|
6
|
+
PADROES = {
|
7
|
+
'pt' => %w[Dado Quando Então E],
|
8
|
+
'en' => %w[Given When Then And]
|
9
|
+
}
|
10
|
+
|
11
|
+
TIPOS_BLOCOS = %w[
|
12
|
+
CONTEXT SUCCESS FAILURE ERROR EXCEPTION
|
13
|
+
VALIDATION PERMISSION EDGE_CASE PERFORMANCE
|
14
|
+
EXAMPLES REGRA RULE
|
15
|
+
]
|
16
|
+
|
17
|
+
def self.gerar_passos(historia, nome_arquivo_feature)
|
18
|
+
idioma = historia[:idioma] || 'pt'
|
19
|
+
conectores = PADROES[idioma]
|
20
|
+
passos_gerados = []
|
21
|
+
|
22
|
+
grupos_examples = dividir_examples(historia[:blocos]["EXAMPLES"]) if historia[:blocos]["EXAMPLES"]&.any?
|
23
|
+
|
24
|
+
TIPOS_BLOCOS.each do |tipo|
|
25
|
+
blocos = tipo == "REGRA" || tipo == "RULE" ? historia[:regras] : historia[:blocos][tipo]
|
26
|
+
next unless blocos.is_a?(Array)
|
27
|
+
|
28
|
+
passos = blocos.dup
|
29
|
+
|
30
|
+
passos.each do |linha|
|
31
|
+
conector = conectores.find { |c| linha.strip.start_with?(c) }
|
32
|
+
next unless conector
|
33
|
+
|
34
|
+
corpo = linha.strip.sub(/^#{conector}/, '').strip
|
35
|
+
|
36
|
+
# Sanitiza aspas duplas envolvendo parâmetros, ex: "<nome>" -> <nome>
|
37
|
+
corpo_sanitizado = corpo.gsub(/"(<[^>]+>)"/, '\1')
|
38
|
+
|
39
|
+
# Verifica se este passo pertence a algum grupo de exemplos
|
40
|
+
grupo_exemplo_compat = nil
|
41
|
+
|
42
|
+
if tipo == "SUCCESS" && grupos_examples
|
43
|
+
grupos_examples.each do |grupo|
|
44
|
+
cabecalho = grupo.first.gsub('|', '').split.map(&:strip)
|
45
|
+
if cabecalho.any? { |col| corpo.include?("<#{col}>") }
|
46
|
+
grupo_exemplo_compat = {
|
47
|
+
cabecalho: cabecalho,
|
48
|
+
linhas: grupo[1..].map { |linha| linha.split('|').reject(&:empty?).map(&:strip) }
|
49
|
+
}
|
50
|
+
break
|
51
|
+
end
|
50
52
|
end
|
51
53
|
end
|
52
|
-
end
|
53
54
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
55
|
+
if grupo_exemplo_compat
|
56
|
+
# Substitui cada <param> por {tipo} dinamicamente
|
57
|
+
corpo_parametrizado = corpo_sanitizado.gsub(/<([^>]+)>/) do
|
58
|
+
nome = $1.strip
|
59
|
+
tipo_param = detectar_tipo_param(nome, grupo_exemplo_compat)
|
60
|
+
"{#{tipo_param}}"
|
61
|
+
end
|
62
|
+
|
63
|
+
parametros = corpo.scan(/<([^>]+)>/).flatten.map { |p| p.strip.gsub(' ', '_') }
|
64
|
+
param_list = parametros.join(', ')
|
65
|
+
else
|
66
|
+
corpo_parametrizado = corpo
|
67
|
+
parametros = []
|
68
|
+
param_list = ""
|
60
69
|
end
|
61
70
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
71
|
+
passos_gerados << {
|
72
|
+
conector: conector,
|
73
|
+
raw: corpo,
|
74
|
+
param: corpo_parametrizado,
|
75
|
+
args: param_list,
|
76
|
+
tipo: tipo
|
77
|
+
} unless passos_gerados.any? { |p| p[:param] == corpo_parametrizado }
|
68
78
|
end
|
69
79
|
|
70
|
-
passos_gerados << {
|
71
|
-
conector: conector,
|
72
|
-
raw: corpo,
|
73
|
-
param: corpo_parametrizado,
|
74
|
-
args: param_list,
|
75
|
-
tipo: tipo
|
76
|
-
} unless passos_gerados.any? { |p| p[:param] == corpo_parametrizado }
|
77
80
|
end
|
78
81
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
return false
|
84
|
-
end
|
82
|
+
if passos_gerados.empty?
|
83
|
+
puts "⚠️ Nenhum passo detectado em: #{nome_arquivo_feature} (arquivo não gerado)"
|
84
|
+
return false
|
85
|
+
end
|
85
86
|
|
86
|
-
|
87
|
-
|
88
|
-
|
87
|
+
nome_base = File.basename(nome_arquivo_feature, '.feature')
|
88
|
+
caminho = "steps/#{nome_base}_steps.rb"
|
89
|
+
FileUtils.mkdir_p(File.dirname(caminho))
|
89
90
|
|
90
|
-
|
91
|
-
|
92
|
-
|
91
|
+
comentario = "# Step definitions para #{File.basename(nome_arquivo_feature)}"
|
92
|
+
comentario += idioma == 'en' ? " (English)" : " (Português)"
|
93
|
+
conteudo = "#{comentario}\n\n"
|
93
94
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
95
|
+
passos_gerados.each do |passo|
|
96
|
+
conteudo += <<~STEP
|
97
|
+
#{passo[:conector]}('#{passo[:param]}') do#{passo[:args].empty? ? '' : " |#{passo[:args]}|"}
|
98
|
+
pending '#{idioma == 'en' ? 'Implement step' : 'Implementar passo'}: #{passo[:raw]}'
|
99
|
+
end
|
100
|
+
|
101
|
+
STEP
|
98
102
|
end
|
99
103
|
|
100
|
-
|
104
|
+
FileUtils.mkdir_p("steps")
|
105
|
+
if Bddgenx::Verificador.gerar_arquivo_se_novo(caminho, conteudo)
|
106
|
+
puts "✅ Step definitions gerados: #{caminho}"
|
107
|
+
else
|
108
|
+
puts "⏭️ Steps mantidos: #{caminho}"
|
109
|
+
end
|
110
|
+
true
|
101
111
|
end
|
102
112
|
|
103
|
-
FileUtils.mkdir_p("steps")
|
104
|
-
if Bddgenx::Verificador.gerar_arquivo_se_novo(caminho, conteudo)
|
105
|
-
puts "✅ Step definitions gerados: #{caminho}"
|
106
|
-
else
|
107
|
-
puts "⏭️ Steps mantidos: #{caminho}"
|
108
|
-
end
|
109
|
-
true
|
110
|
-
end
|
111
113
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
114
|
+
def self.substituir_parametros(texto, exemplos)
|
115
|
+
texto.gsub(/<([^>]+)>/) do |_match|
|
116
|
+
nome = $1.strip
|
117
|
+
tipo = detectar_tipo_param(nome, exemplos)
|
118
|
+
"{#{tipo}}"
|
119
|
+
end
|
118
120
|
end
|
119
|
-
end
|
120
121
|
|
121
|
-
|
122
|
-
|
122
|
+
def self.detectar_tipo_param(nome_coluna, exemplos)
|
123
|
+
return 'string' unless exemplos && exemplos[:cabecalho].include?(nome_coluna)
|
123
124
|
|
124
|
-
|
125
|
-
|
125
|
+
idx = exemplos[:cabecalho].index(nome_coluna)
|
126
|
+
valores = exemplos[:linhas].map { |linha| linha[idx].to_s.strip }
|
126
127
|
|
127
|
-
|
128
|
-
|
129
|
-
|
128
|
+
return 'boolean' if valores.all? { |v| %w[true false].include?(v.downcase) }
|
129
|
+
return 'int' if valores.all? { |v| v.match?(/^\d+$/) }
|
130
|
+
return 'float' if valores.all? { |v| v.match?(/^\d+\.\d+$/) }
|
130
131
|
|
131
|
-
|
132
|
-
|
132
|
+
'string'
|
133
|
+
end
|
133
134
|
|
134
|
-
|
135
|
-
|
136
|
-
|
135
|
+
def self.dividir_examples(tabela_bruta)
|
136
|
+
grupos = []
|
137
|
+
grupo_atual = []
|
137
138
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
139
|
+
tabela_bruta.each do |linha|
|
140
|
+
if linha.strip =~ /^\|\s*[\w\s]+\|/ && grupo_atual.any? && linha.strip == linha.strip.squeeze(" ")
|
141
|
+
grupos << grupo_atual
|
142
|
+
grupo_atual = [linha]
|
143
|
+
else
|
144
|
+
grupo_atual << linha
|
145
|
+
end
|
144
146
|
end
|
145
|
-
end
|
146
147
|
|
147
|
-
|
148
|
-
|
149
|
-
|
148
|
+
grupos << grupo_atual unless grupo_atual.empty?
|
149
|
+
grupos
|
150
|
+
end
|
150
151
|
|
151
152
|
|
152
|
-
|
153
|
-
|
153
|
+
def self.extrair_exemplos(bloco)
|
154
|
+
return nil unless bloco&.any?
|
154
155
|
|
155
|
-
|
156
|
-
|
157
|
-
|
156
|
+
linhas = bloco.map(&:strip)
|
157
|
+
cabecalho = linhas.first.gsub('|', '').split.map(&:strip)
|
158
|
+
dados = linhas[1..].map { |linha| linha.gsub('|', '').split.map(&:strip) }
|
158
159
|
|
159
|
-
|
160
|
+
{ cabecalho: cabecalho, linhas: dados }
|
161
|
+
end
|
160
162
|
end
|
161
163
|
end
|
162
164
|
|
data/lib/bddgenx/tracer.rb
CHANGED
@@ -1,31 +1,33 @@
|
|
1
1
|
require 'csv'
|
2
2
|
require 'fileutils'
|
3
3
|
|
4
|
-
module
|
5
|
-
|
4
|
+
module Bddgenx
|
5
|
+
class Tracer
|
6
|
+
ARQUIVO = 'output/rastreabilidade.csv'
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
def self.adicionar_entrada(historia, caminho_arquivo)
|
9
|
+
FileUtils.mkdir_p("output")
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
CSV.open(ARQUIVO, File.exist?(ARQUIVO) ? 'a' : 'w', col_sep: ';') do |csv|
|
12
|
+
unless File.exist?(ARQUIVO)
|
13
|
+
csv << ["Funcionalidade", "Tipo de Teste", "Nome do Cenário", "Arquivo .feature"]
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
historia[:blocos].each do |tipo, passos|
|
17
|
+
next if tipo == "CONTEXT" || tipo == "EXAMPLES" || passos.empty?
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
tipo_istqb = tipo.capitalize.gsub('_', ' ').capitalize
|
20
|
+
contexto = passos.first&.gsub(/^(Dado que|Quando|Então|E)/, '')&.strip || "Condição"
|
21
|
+
resultado = passos.last&.gsub(/^(Então|E)/, '')&.strip || "Resultado"
|
22
|
+
nome_cenario = "#{tipo_istqb} - #{contexto} - #{resultado}"
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
csv << [
|
25
|
+
historia[:quero].sub(/^Quero/, '').strip,
|
26
|
+
tipo_istqb,
|
27
|
+
nome_cenario,
|
28
|
+
caminho_arquivo
|
29
|
+
]
|
30
|
+
end
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|
data/lib/bddgenx/validator.rb
CHANGED
@@ -1,32 +1,34 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
1
|
+
module Bddgenx
|
2
|
+
class Validator
|
3
|
+
TIPOS_CENARIO = %w[
|
4
|
+
SUCCESS
|
5
|
+
FAILURE
|
6
|
+
ERROR
|
7
|
+
EXCEPTION
|
8
|
+
VALIDATION
|
9
|
+
PERMISSION
|
10
|
+
EDGE_CASE
|
11
|
+
PERFORMANCE
|
12
|
+
]
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
def self.validar(historia)
|
15
|
+
valido = true
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
if historia[:como].to_s.strip.empty? ||
|
18
|
+
historia[:quero].to_s.strip.empty? ||
|
19
|
+
historia[:para].to_s.strip.empty?
|
20
|
+
puts "❌ História incompleta: 'Como', 'Quero' ou 'Para' está faltando."
|
21
|
+
valido = false
|
22
|
+
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
cenarios_presentes = historia[:blocos].keys & TIPOS_CENARIO
|
25
|
+
valido ||= historia[:blocos]["CONTEXT"]&.any? || historia[:regras]&.any?
|
26
|
+
if cenarios_presentes.empty? && !valido
|
27
|
+
puts "❌ Nenhum conteúdo válido detectado (cenários, contexto ou regras)."
|
28
|
+
return false
|
29
|
+
end
|
29
30
|
|
30
|
-
|
31
|
+
valido
|
32
|
+
end
|
31
33
|
end
|
32
34
|
end
|
data/lib/bddgenx/version.rb
CHANGED
data/lib/bddgenx.rb
CHANGED
@@ -15,26 +15,26 @@ cont_steps = 0
|
|
15
15
|
cont_ignorados = 0
|
16
16
|
|
17
17
|
# Exibe menu inicial e pergunta quais arquivos processar
|
18
|
-
arquivos = CLI.selecionar_arquivos_txt('input')
|
18
|
+
arquivos = Bddgenx::CLI.selecionar_arquivos_txt('input')
|
19
19
|
|
20
20
|
arquivos.each do |arquivo_path|
|
21
21
|
puts "\n🔍 Processando: #{arquivo_path}"
|
22
22
|
|
23
23
|
historia = Bddgenx::Parser.ler_historia(arquivo_path)
|
24
24
|
|
25
|
-
unless Validator.validar(historia)
|
25
|
+
unless Bddgenx::Validator.validar(historia)
|
26
26
|
cont_ignorados += 1
|
27
27
|
puts "❌ Arquivo inválido: #{arquivo_path}"
|
28
28
|
next
|
29
29
|
end
|
30
30
|
|
31
|
-
nome_feature, conteudo_feature = Generator.gerar_feature(historia)
|
31
|
+
nome_feature, conteudo_feature = Bddgenx::Generator.gerar_feature(historia)
|
32
32
|
|
33
|
-
|
34
|
-
cont_features += 1 if Generator.salvar_feature(nome_feature, conteudo_feature)
|
35
|
-
cont_steps += 1 if StepsGenerator.gerar_passos(historia, nome_feature)
|
33
|
+
Bddgenx::Backup.salvar_versao_antiga(nome_feature)
|
34
|
+
cont_features += 1 if Bddgenx::Generator.salvar_feature(nome_feature, conteudo_feature)
|
35
|
+
cont_steps += 1 if Bddgenx::StepsGenerator.gerar_passos(historia, nome_feature)
|
36
36
|
|
37
|
-
Tracer.adicionar_entrada(historia, nome_feature)
|
37
|
+
Bddgenx::Tracer.adicionar_entrada(historia, nome_feature)
|
38
38
|
Bddgenx::PDFExporter.exportar_todos
|
39
39
|
end
|
40
40
|
puts "\n✅ Processamento finalizado. Arquivos gerados em: features/, steps/, output/"
|