bddgenx 0.1.16 → 0.1.18
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/Rakefile +54 -0
- data/lib/bddgenx/generator.rb +38 -109
- data/lib/bddgenx/parser.rb +44 -21
- data/lib/bddgenx/pdf_exporter.rb +12 -11
- data/lib/bddgenx/steps_generator.rb +49 -83
- data/lib/bddgenx/tracer.rb +29 -17
- data/lib/bddgenx/validator.rb +21 -22
- data/lib/bddgenx/version.rb +1 -1
- metadata +6 -5
- /data/{assets → lib/bddgenx/assets}/fonts/DejaVuSansMono-Bold.ttf +0 -0
- /data/{assets → lib/bddgenx/assets}/fonts/DejaVuSansMono-BoldOblique.ttf +0 -0
- /data/{assets → lib/bddgenx/assets}/fonts/DejaVuSansMono-Oblique.ttf +0 -0
- /data/{assets → lib/bddgenx/assets}/fonts/DejaVuSansMono.ttf +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 94a0f0c3d58d4244048cc2b01054fbdde3ffaf2c627d36ea531d25bb64fd295d
|
4
|
+
data.tar.gz: fa9590c3a6e81c863ae6ea706e7bd24e0577cf9b31ef851c868f8c5a81c181bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a6cb0637977eab5b05790e0b0900d9862ebd4a7633b24d2a8d0c1daf0b5ff1259adcc1110f51e89607d07a335cb80f3cd05cca958c24332f93651d120a2eba4a
|
7
|
+
data.tar.gz: df1f2fe658088fb4fa27cea0be63547228d1ac677333191cbc68a59bd59c5b58ae056b75e3c4f7017b43b00879244fa63e81f3da905771f63692322a7397b133
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require_relative 'lib/bddgenx.rb'
|
3
|
+
require_relative 'lib/bddgenx/pdf_exporter'
|
4
|
+
require_relative 'lib/bddgenx/integrations/jira'
|
5
|
+
require_relative 'lib/bddgenx/integrations/testlink'
|
6
|
+
|
7
|
+
namespace :bddgenx do
|
8
|
+
|
9
|
+
desc "Gerar arquivos .feature, steps, rastreabilidade e backups"
|
10
|
+
task :gerar do
|
11
|
+
arquivos = Bddgenx::CLI.todos_arquivos('input')
|
12
|
+
arquivos.each do |arquivo|
|
13
|
+
puts "🔁 Executando: ruby bddgen.rb"
|
14
|
+
system("ruby lib/bddgen.rb")
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "Exportar todos os arquivos .feature para PDF"
|
20
|
+
task :pdf do
|
21
|
+
puts "📦 Exportando para PDF..."
|
22
|
+
Bddgenx::PDFExporter.exportar_todos
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Enviar todos os cenários para o Jira"
|
26
|
+
task :jira do
|
27
|
+
jira = Bddgenx::Integrations::Jira.new(
|
28
|
+
username: ENV['JIRA_USER'],
|
29
|
+
api_token: ENV['JIRA_TOKEN'],
|
30
|
+
site: ENV['JIRA_SITE'],
|
31
|
+
project_key: ENV['JIRA_PROJECT']
|
32
|
+
)
|
33
|
+
|
34
|
+
Dir.glob("features/*.feature") do |arquivo|
|
35
|
+
conteudo = File.read(arquivo)
|
36
|
+
titulo = File.basename(arquivo, ".feature").gsub('_', ' ').capitalize
|
37
|
+
jira.enviar_cenario(titulo, conteudo)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "Enviar todos os cenários para o TestLink"
|
42
|
+
task :testlink do
|
43
|
+
testlink = Bddgen::Integrations::TestLink.new(
|
44
|
+
ENV['TESTLINK_TOKEN'],
|
45
|
+
ENV['TESTLINK_URL']
|
46
|
+
)
|
47
|
+
|
48
|
+
Dir.glob("features/*.feature") do |arquivo|
|
49
|
+
conteudo = File.readlines(arquivo).reject { |l| l.strip.start_with?("#") || l.strip.empty? }
|
50
|
+
titulo = File.basename(arquivo, ".feature").gsub('_', ' ').capitalize
|
51
|
+
testlink.criar_caso_teste(ENV['TESTLINK_PLAN_ID'].to_i, titulo, conteudo)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/bddgenx/generator.rb
CHANGED
@@ -11,141 +11,70 @@ module Bddgenx
|
|
11
11
|
"PERMISSION" => "Teste de Permissão",
|
12
12
|
"EDGE_CASE" => "Teste de Limite",
|
13
13
|
"PERFORMANCE" => "Teste de Desempenho"
|
14
|
-
}
|
15
|
-
|
16
|
-
TIPOS_CENARIO = %w[
|
17
|
-
SUCCESS FAILURE ERROR EXCEPTION
|
18
|
-
VALIDATION PERMISSION EDGE_CASE PERFORMANCE
|
19
|
-
]
|
20
|
-
|
14
|
+
}.freeze
|
21
15
|
|
22
16
|
def self.gerar_feature(historia)
|
23
|
-
idioma = historia[:idioma]
|
24
|
-
|
25
|
-
# Define os conectores de acordo com o idioma
|
17
|
+
idioma = historia[:idioma]
|
26
18
|
palavras = {
|
27
|
-
contexto:
|
28
|
-
cenario:
|
29
|
-
esquema:
|
30
|
-
exemplos:
|
31
|
-
regra:
|
19
|
+
contexto: idioma == 'en' ? 'Background' : 'Contexto',
|
20
|
+
cenario: idioma == 'en' ? 'Scenario' : 'Cenário',
|
21
|
+
esquema: idioma == 'en' ? 'Scenario Outline' : 'Esquema do Cenário',
|
22
|
+
exemplos: idioma == 'en' ? 'Examples' : 'Exemplos',
|
23
|
+
regra: idioma == 'en' ? 'Rule' : 'Regra'
|
32
24
|
}
|
33
25
|
|
34
|
-
nome_base = historia[:quero].gsub(/[^a-
|
35
|
-
caminho
|
26
|
+
nome_base = historia[:quero].gsub(/[^a-z0-9]/i, '_').downcase
|
27
|
+
caminho = "features/#{nome_base}.feature"
|
36
28
|
|
37
|
-
conteudo = <<~
|
29
|
+
conteudo = <<~GHERKIN
|
38
30
|
# language: #{idioma}
|
39
|
-
Funcionalidade: #{historia[:quero].sub(/^Quero
|
40
|
-
|
31
|
+
Funcionalidade: #{historia[:quero].sub(/^Quero\s*/, '')}
|
32
|
+
|
41
33
|
#{historia[:como]}
|
42
34
|
#{historia[:quero]}
|
43
35
|
#{historia[:para]}
|
44
|
-
|
45
|
-
HEADER
|
46
36
|
|
47
|
-
|
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
|
37
|
+
GHERKIN
|
55
38
|
|
39
|
+
historia[:grupos].each_with_index do |grupo, idx|
|
40
|
+
tipo = grupo[:tipo]
|
41
|
+
tag = grupo[:tag]
|
42
|
+
passos = grupo[:passos]
|
43
|
+
exemplos = grupo[:exemplos]
|
56
44
|
|
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"
|
62
|
-
end
|
63
|
-
|
64
|
-
# Cenários
|
65
|
-
TIPOS_CENARIO.each do |tipo|
|
66
|
-
passos = historia[:blocos][tipo]
|
67
|
-
passos = passos&.reject { |l| l.strip.empty? } || []
|
68
45
|
next if passos.empty?
|
69
46
|
|
70
|
-
|
71
|
-
|
72
|
-
possui_parametros = passos.any? { |p| p.include?('<') }
|
73
|
-
next if possui_parametros
|
74
|
-
end
|
75
|
-
|
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}"
|
80
|
-
|
81
|
-
conteudo += " @#{tipo.downcase}\n"
|
82
|
-
conteudo += " #{palavras[:cenario]}: #{nome_cenario}\n"
|
83
|
-
passos.each { |p| conteudo += " #{p}\n" }
|
84
|
-
conteudo += "\n"
|
85
|
-
end
|
86
|
-
|
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)
|
91
|
-
|
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) }
|
95
|
-
|
96
|
-
exemplos = { cabecalho: cabecalho, linhas: linhas }
|
47
|
+
linha_tag = ["@#{tipo.downcase}", ("@#{tag}" if tag)].compact.join(' ')
|
48
|
+
possui_parametros = passos.any? { |p| p.include?('<') } && exemplos.any?
|
97
49
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
conteudo
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
conteudo
|
112
|
-
conteudo
|
113
|
-
conteudo
|
114
|
-
|
50
|
+
if possui_parametros
|
51
|
+
conteudo << " #{linha_tag}\n"
|
52
|
+
conteudo << " #{palavras[:esquema]}: Exemplo #{idx + 1}\n"
|
53
|
+
passos.each { |p| conteudo << " #{p}\n" }
|
54
|
+
conteudo << "\n #{palavras[:exemplos]}:\n"
|
55
|
+
exemplos.each { |linha| conteudo << " #{linha}\n" }
|
56
|
+
conteudo << "\n"
|
57
|
+
else
|
58
|
+
nome_teste = TIPOS_ISTQB[tipo] || palavras[:cenario]
|
59
|
+
contexto = passos.first.gsub(/^(Dado|Quando|Então|E|Mas)\s+/, '').strip
|
60
|
+
resultado = passos.last .gsub(/^(Dado|Quando|Então|E|Mas)\s+/, '').strip
|
61
|
+
nome_ceno = "#{nome_teste} - #{contexto} - #{resultado}"
|
62
|
+
|
63
|
+
conteudo << " #{linha_tag}\n"
|
64
|
+
conteudo << " #{palavras[:cenario]}: #{nome_ceno}\n"
|
65
|
+
passos.each { |p| conteudo << " #{p}\n" }
|
66
|
+
conteudo << "\n"
|
115
67
|
end
|
116
68
|
end
|
117
69
|
|
118
70
|
[caminho, conteudo]
|
119
71
|
end
|
120
72
|
|
121
|
-
# Salva o arquivo .feature gerado
|
122
|
-
# Retorna true se o arquivo foi salvo com sucesso, false caso contrário
|
123
73
|
def self.salvar_feature(caminho, conteudo)
|
124
|
-
|
125
|
-
puts "⚠️ Nenhum conteúdo gerado para: #{caminho} (ignorado)"
|
126
|
-
return false
|
127
|
-
end
|
128
|
-
|
74
|
+
FileUtils.mkdir_p(File.dirname(caminho))
|
129
75
|
File.write(caminho, conteudo)
|
130
76
|
puts "✅ Arquivo .feature gerado: #{caminho}"
|
131
77
|
true
|
132
78
|
end
|
133
|
-
|
134
|
-
def self.dividir_examples(tabela_bruta)
|
135
|
-
grupos = []
|
136
|
-
grupo_atual = []
|
137
|
-
|
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
|
145
|
-
end
|
146
|
-
|
147
|
-
grupos << grupo_atual unless grupo_atual.empty?
|
148
|
-
grupos
|
149
|
-
end
|
150
79
|
end
|
151
80
|
end
|
data/lib/bddgenx/parser.rb
CHANGED
@@ -4,46 +4,69 @@ module Bddgenx
|
|
4
4
|
VALIDATION PERMISSION EDGE_CASE PERFORMANCE
|
5
5
|
EXAMPLES REGRA RULE
|
6
6
|
].freeze
|
7
|
+
|
7
8
|
class Parser
|
8
9
|
def self.ler_historia(caminho_arquivo)
|
9
|
-
linhas = File.readlines(caminho_arquivo, encoding: 'utf-8')
|
10
|
+
linhas = File.readlines(caminho_arquivo, encoding: 'utf-8')
|
11
|
+
.map(&:strip)
|
12
|
+
.reject(&:empty?)
|
10
13
|
|
11
|
-
#
|
14
|
+
# idioma
|
12
15
|
idioma = linhas.first.downcase.include?('# lang: en') ? 'en' : 'pt'
|
13
|
-
linhas.shift if linhas.first.downcase.start_with?('#')
|
16
|
+
linhas.shift if linhas.first.downcase.start_with?('# lang:')
|
14
17
|
|
15
|
-
#
|
16
|
-
cabecalho = []
|
18
|
+
# cabeçalho Gherkin: Como / Quero / Para
|
17
19
|
until linhas.empty?
|
18
20
|
linha = linhas.shift
|
19
|
-
break if linha
|
21
|
+
break if linha =~ /^(Como |As a )/
|
20
22
|
end
|
21
|
-
como
|
23
|
+
como = linha
|
22
24
|
quero = linhas.shift
|
23
|
-
para
|
25
|
+
para = linhas.shift
|
24
26
|
|
25
27
|
historia = {
|
26
|
-
como:
|
27
|
-
quero:
|
28
|
-
para:
|
29
|
-
|
30
|
-
|
31
|
-
arquivo_origem: caminho_arquivo,
|
32
|
-
idioma: idioma
|
28
|
+
como: como,
|
29
|
+
quero: quero,
|
30
|
+
para: para,
|
31
|
+
idioma: idioma,
|
32
|
+
grupos: [] # cada bloco ([TIPO]@tag) será um grupo
|
33
33
|
}
|
34
34
|
|
35
|
-
|
35
|
+
exemplos_mode = false
|
36
|
+
tipo_atual = nil
|
37
|
+
tag_atual = nil
|
36
38
|
|
37
39
|
linhas.each do |linha|
|
38
|
-
|
39
|
-
|
40
|
+
# início de bloco EXAMPLES: modo exemplos para último grupo
|
41
|
+
if linha =~ /^\[EXAMPLES\](?:@(\w+))?$/
|
42
|
+
exemplos_mode = true
|
43
|
+
raise "Formato inválido: EXAMPLES sem bloco anterior" if historia[:grupos].empty?
|
44
|
+
historia[:grupos].last[:exemplos] = []
|
40
45
|
next
|
41
46
|
end
|
42
47
|
|
43
|
-
|
44
|
-
|
48
|
+
# início de um novo bloco (exceto EXAMPLES)
|
49
|
+
if linha =~ /^\[(#{TIPOS_BLOCOS.join('|')})\](?:@(\w+))?$/
|
50
|
+
exemplos_mode = false
|
51
|
+
tipo_atual = $1
|
52
|
+
tag_atual = $2
|
53
|
+
historia[:grupos] << {
|
54
|
+
tipo: tipo_atual,
|
55
|
+
tag: tag_atual,
|
56
|
+
passos: [],
|
57
|
+
exemplos: []
|
58
|
+
}
|
59
|
+
next
|
60
|
+
end
|
61
|
+
|
62
|
+
# atribuir linhas ao bloco atual
|
63
|
+
next if historia[:grupos].empty?
|
64
|
+
atual = historia[:grupos].last
|
65
|
+
|
66
|
+
if exemplos_mode
|
67
|
+
atual[:exemplos] << linha
|
45
68
|
else
|
46
|
-
|
69
|
+
atual[:passos] << linha
|
47
70
|
end
|
48
71
|
end
|
49
72
|
|
data/lib/bddgenx/pdf_exporter.rb
CHANGED
@@ -13,8 +13,9 @@ module Bddgenx
|
|
13
13
|
if File.exist?(destino)
|
14
14
|
puts "⚠️ PDF já existente: #{destino} — pulando geração."
|
15
15
|
return
|
16
|
+
else
|
17
|
+
puts "📄 PDF gerado: #{destino}"
|
16
18
|
end
|
17
|
-
puts "📄 PDF gerado: #{destino}"
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
@@ -22,22 +23,22 @@ module Bddgenx
|
|
22
23
|
conteudo = File.read(origem, encoding: 'utf-8')
|
23
24
|
|
24
25
|
Prawn::Document.generate(destino) do |pdf|
|
25
|
-
fonte_existe = File.exist?("fonts/DejaVuSansMono.ttf")
|
26
|
-
|
27
|
-
|
28
|
-
if
|
26
|
+
fonte_existe = File.exist?("assets/fonts/DejaVuSansMono.ttf")
|
27
|
+
font_dir = File.expand_path("assets/fonts", __dir__)
|
28
|
+
|
29
|
+
if File.exist?(File.join(font_dir, "DejaVuSansMono.ttf"))
|
29
30
|
pdf.font_families.update(
|
30
31
|
"DejaVu" => {
|
31
|
-
normal: File.join(
|
32
|
-
bold: File.join(
|
33
|
-
italic: File.join(
|
34
|
-
bold_italic: File.join(
|
32
|
+
normal: File.join(font_dir, "DejaVuSansMono.ttf"),
|
33
|
+
bold: File.join(font_dir, "DejaVuSansMono-Bold.ttf"),
|
34
|
+
italic: File.join(font_dir, "DejaVuSansMono-Oblique.ttf"),
|
35
|
+
bold_italic: File.join(font_dir, "DejaVuSansMono-BoldOblique.ttf")
|
35
36
|
}
|
36
37
|
)
|
37
38
|
pdf.font "DejaVu"
|
38
39
|
else
|
39
|
-
puts "⚠️
|
40
|
-
pdf.font "Courier"
|
40
|
+
puts "⚠️ Fonte não encontrada: #{font_dir}"
|
41
|
+
pdf.font "Courier"
|
41
42
|
end
|
42
43
|
|
43
44
|
pdf.font_size 10
|
@@ -4,79 +4,68 @@ require_relative 'utils/verificador'
|
|
4
4
|
module Bddgenx
|
5
5
|
class StepsGenerator
|
6
6
|
PADROES = {
|
7
|
-
'pt' => %w[Dado Quando Então E],
|
8
|
-
'en' => %w[Given When Then And]
|
7
|
+
'pt' => %w[Dado Quando Então E Mas],
|
8
|
+
'en' => %w[Given When Then And But]
|
9
9
|
}
|
10
10
|
|
11
|
-
|
12
|
-
CONTEXT SUCCESS FAILURE ERROR EXCEPTION
|
13
|
-
VALIDATION PERMISSION EDGE_CASE PERFORMANCE
|
14
|
-
EXAMPLES REGRA RULE
|
15
|
-
]
|
16
|
-
|
11
|
+
# Gera step definitions a partir da estrutura historia[:grupos]
|
17
12
|
def self.gerar_passos(historia, nome_arquivo_feature)
|
18
13
|
idioma = historia[:idioma] || 'pt'
|
19
14
|
conectores = PADROES[idioma]
|
20
15
|
passos_gerados = []
|
21
16
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
17
|
+
historia[:grupos].each do |grupo|
|
18
|
+
tipo = grupo[:tipo]
|
19
|
+
passos = grupo[:passos]
|
20
|
+
exemplos_brutos = grupo[:exemplos]
|
21
|
+
exemplos = exemplos_brutos&.any? ? dividir_examples(exemplos_brutos) : nil
|
27
22
|
|
28
|
-
passos
|
23
|
+
next unless passos.is_a?(Array) && passos.any?
|
29
24
|
|
30
25
|
passos.each do |linha|
|
31
26
|
conector = conectores.find { |c| linha.strip.start_with?(c) }
|
32
27
|
next unless conector
|
33
28
|
|
34
|
-
corpo
|
35
|
-
|
36
|
-
# Sanitiza aspas duplas envolvendo parâmetros, ex: "<nome>" -> <nome>
|
37
|
-
corpo_sanitizado = corpo.gsub(/"(<[^>]+>)"/, '\1')
|
29
|
+
corpo = linha.strip.sub(/^#{conector}\s*/, '')
|
30
|
+
corpo_sanitizado = corpo.gsub(/"(<[^>]+>)"/, '\1')
|
38
31
|
|
39
|
-
#
|
32
|
+
# tenta encontrar grupo de exemplos compatível, se existir
|
40
33
|
grupo_exemplo_compat = nil
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
cabecalho = grupo.first.gsub('|', '').split.map(&:strip)
|
34
|
+
if exemplos
|
35
|
+
exemplos.each do |tabela|
|
36
|
+
cabecalho = tabela.first.gsub('|', '').split.map(&:strip)
|
45
37
|
if cabecalho.any? { |col| corpo.include?("<#{col}>") }
|
46
|
-
|
47
|
-
|
48
|
-
linhas: grupo[1..].map { |linha| linha.split('|').reject(&:empty?).map(&:strip) }
|
49
|
-
}
|
38
|
+
linhas = tabela[1..].map { |l| l.gsub('|', '').split.map(&:strip) }
|
39
|
+
grupo_exemplo_compat = { cabecalho: cabecalho, linhas: linhas }
|
50
40
|
break
|
51
41
|
end
|
52
42
|
end
|
53
43
|
end
|
54
44
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
45
|
+
# detecta todos os parametros <...> e gera arg list
|
46
|
+
nomes_param = corpo.scan(/<([^>]+)>/).flatten.map(&:strip)
|
47
|
+
if nomes_param.any?
|
48
|
+
corpo_param = corpo_sanitizado.dup
|
49
|
+
nomes_param.each do |nome|
|
50
|
+
tipo_param = grupo_exemplo_compat ? detectar_tipo_param(nome, grupo_exemplo_compat) : 'string'
|
51
|
+
corpo_param.gsub!(/<\s*#{Regexp.escape(nome)}\s*>/, "{#{tipo_param}}")
|
61
52
|
end
|
62
|
-
|
63
|
-
|
64
|
-
param_list = parametros.join(', ')
|
53
|
+
args_list = nomes_param.map { |p| p.gsub(/\s+/, '_') }.join(', ')
|
54
|
+
pending_msg = corpo
|
65
55
|
else
|
66
|
-
|
67
|
-
|
68
|
-
|
56
|
+
corpo_param = corpo
|
57
|
+
args_list = ''
|
58
|
+
pending_msg = corpo
|
69
59
|
end
|
70
60
|
|
71
61
|
passos_gerados << {
|
72
62
|
conector: conector,
|
73
|
-
raw:
|
74
|
-
param:
|
75
|
-
args:
|
76
|
-
tipo:
|
77
|
-
} unless passos_gerados.any? { |p| p[:param] ==
|
63
|
+
raw: pending_msg,
|
64
|
+
param: corpo_param,
|
65
|
+
args: args_list,
|
66
|
+
tipo: tipo
|
67
|
+
} unless passos_gerados.any? { |p| p[:param] == corpo_param }
|
78
68
|
end
|
79
|
-
|
80
69
|
end
|
81
70
|
|
82
71
|
if passos_gerados.empty?
|
@@ -85,7 +74,7 @@ module Bddgenx
|
|
85
74
|
end
|
86
75
|
|
87
76
|
nome_base = File.basename(nome_arquivo_feature, '.feature')
|
88
|
-
caminho
|
77
|
+
caminho = "steps/#{nome_base}_steps.rb"
|
89
78
|
FileUtils.mkdir_p(File.dirname(caminho))
|
90
79
|
|
91
80
|
comentario = "# Step definitions para #{File.basename(nome_arquivo_feature)}"
|
@@ -94,14 +83,13 @@ module Bddgenx
|
|
94
83
|
|
95
84
|
passos_gerados.each do |passo|
|
96
85
|
conteudo += <<~STEP
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
86
|
+
#{passo[:conector]}('#{passo[:param]}') do#{passo[:args].empty? ? '' : " |#{passo[:args]}|"}
|
87
|
+
pending 'Implementar passo: #{passo[:raw]}'
|
88
|
+
end
|
89
|
+
|
101
90
|
STEP
|
102
91
|
end
|
103
92
|
|
104
|
-
FileUtils.mkdir_p("steps")
|
105
93
|
if Bddgenx::Verificador.gerar_arquivo_se_novo(caminho, conteudo)
|
106
94
|
puts "✅ Step definitions gerados: #{caminho}"
|
107
95
|
else
|
@@ -110,20 +98,12 @@ module Bddgenx
|
|
110
98
|
true
|
111
99
|
end
|
112
100
|
|
113
|
-
|
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
|
120
|
-
end
|
121
|
-
|
101
|
+
# Detecta tipo do parâmetro baseado em exemplos ou default
|
122
102
|
def self.detectar_tipo_param(nome_coluna, exemplos)
|
123
|
-
return 'string' unless exemplos
|
103
|
+
return 'string' unless exemplos[:cabecalho].include?(nome_coluna)
|
124
104
|
|
125
105
|
idx = exemplos[:cabecalho].index(nome_coluna)
|
126
|
-
valores = exemplos[:linhas].map { |
|
106
|
+
valores = exemplos[:linhas].map { |l| l[idx].to_s.strip }
|
127
107
|
|
128
108
|
return 'boolean' if valores.all? { |v| %w[true false].include?(v.downcase) }
|
129
109
|
return 'int' if valores.all? { |v| v.match?(/^\d+$/) }
|
@@ -132,34 +112,20 @@ module Bddgenx
|
|
132
112
|
'string'
|
133
113
|
end
|
134
114
|
|
115
|
+
# Divide múltiplas tabelas de exemplo em grupos
|
135
116
|
def self.dividir_examples(tabela_bruta)
|
136
117
|
grupos = []
|
137
|
-
|
138
|
-
|
118
|
+
grupo = []
|
139
119
|
tabela_bruta.each do |linha|
|
140
|
-
if linha.strip =~
|
141
|
-
grupos <<
|
142
|
-
|
120
|
+
if linha.strip =~ /^\|.*\|$/ && grupo.any? && linha.strip == linha.strip.squeeze(' ')
|
121
|
+
grupos << grupo
|
122
|
+
grupo = [linha]
|
143
123
|
else
|
144
|
-
|
124
|
+
grupo << linha
|
145
125
|
end
|
146
126
|
end
|
147
|
-
|
148
|
-
grupos << grupo_atual unless grupo_atual.empty?
|
127
|
+
grupos << grupo unless grupo.empty?
|
149
128
|
grupos
|
150
129
|
end
|
151
|
-
|
152
|
-
|
153
|
-
def self.extrair_exemplos(bloco)
|
154
|
-
return nil unless bloco&.any?
|
155
|
-
|
156
|
-
linhas = bloco.map(&:strip)
|
157
|
-
cabecalho = linhas.first.gsub('|', '').split.map(&:strip)
|
158
|
-
dados = linhas[1..].map { |linha| linha.gsub('|', '').split.map(&:strip) }
|
159
|
-
|
160
|
-
{ cabecalho: cabecalho, linhas: dados }
|
161
|
-
end
|
162
130
|
end
|
163
131
|
end
|
164
|
-
|
165
|
-
|
data/lib/bddgenx/tracer.rb
CHANGED
@@ -3,32 +3,44 @@ require 'fileutils'
|
|
3
3
|
|
4
4
|
module Bddgenx
|
5
5
|
class Tracer
|
6
|
-
|
6
|
+
def self.adicionar_entrada(historia, nome_arquivo_feature)
|
7
|
+
FileUtils.mkdir_p('output')
|
8
|
+
arquivo_csv = 'output/rastreabilidade.csv'
|
7
9
|
|
8
|
-
|
9
|
-
FileUtils.mkdir_p("output")
|
10
|
+
cabecalho = ['Funcionalidade', 'Tipo', 'Tag', 'Cenário', 'Passo', 'Origem']
|
10
11
|
|
11
|
-
|
12
|
-
unless File.exist?(ARQUIVO)
|
13
|
-
csv << ["Funcionalidade", "Tipo de Teste", "Nome do Cenário", "Arquivo .feature"]
|
14
|
-
end
|
12
|
+
linhas = []
|
15
13
|
|
16
|
-
|
17
|
-
|
14
|
+
historia[:grupos].each_with_index do |grupo, idx|
|
15
|
+
tipo = grupo[:tipo]
|
16
|
+
tag = grupo[:tag]
|
17
|
+
passos = grupo[:passos]
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
resultado = passos.last&.gsub(/^(Então|E)/, '')&.strip || "Resultado"
|
22
|
-
nome_cenario = "#{tipo_istqb} - #{contexto} - #{resultado}"
|
19
|
+
nome_funcionalidade = historia[:quero].gsub(/^Quero\s*/, '').strip
|
20
|
+
nome_cenario = "Cenário #{idx + 1}"
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
passos.each do |passo|
|
23
|
+
linhas << [
|
24
|
+
nome_funcionalidade,
|
25
|
+
tipo,
|
26
|
+
tag || '-',
|
27
27
|
nome_cenario,
|
28
|
-
|
28
|
+
passo,
|
29
|
+
File.basename(nome_arquivo_feature)
|
29
30
|
]
|
30
31
|
end
|
31
32
|
end
|
33
|
+
|
34
|
+
escrever_csv(arquivo_csv, cabecalho, linhas)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.escrever_csv(caminho, cabecalho, linhas)
|
38
|
+
novo_arquivo = !File.exist?(caminho)
|
39
|
+
|
40
|
+
CSV.open(caminho, 'a+', col_sep: ';', force_quotes: true) do |csv|
|
41
|
+
csv << cabecalho if novo_arquivo
|
42
|
+
linhas.each { |linha| csv << linha }
|
43
|
+
end
|
32
44
|
end
|
33
45
|
end
|
34
46
|
end
|
data/lib/bddgenx/validator.rb
CHANGED
@@ -1,34 +1,33 @@
|
|
1
1
|
module Bddgenx
|
2
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
|
-
]
|
13
|
-
|
14
3
|
def self.validar(historia)
|
15
|
-
|
4
|
+
erros = []
|
5
|
+
|
6
|
+
unless historia[:como] && historia[:quero] && historia[:para]
|
7
|
+
erros << "❌ Cabeçalho incompleto (Como, Quero, Para obrigatórios)"
|
8
|
+
end
|
9
|
+
|
10
|
+
if historia[:grupos].empty?
|
11
|
+
erros << "❌ Nenhum grupo de blocos detectado"
|
12
|
+
else
|
13
|
+
historia[:grupos].each_with_index do |grupo, idx|
|
14
|
+
if grupo[:passos].empty? && grupo[:exemplos].empty?
|
15
|
+
erros << "❌ Grupo #{idx + 1} do tipo [#{grupo[:tipo]}] está vazio"
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
valido = false
|
18
|
+
if grupo[:tipo] == "EXAMPLES" && grupo[:exemplos].none? { |l| l.strip.start_with?('|') }
|
19
|
+
erros << "❌ Grupo de EXAMPLES no bloco #{idx + 1} não contém tabela válida"
|
20
|
+
end
|
21
|
+
end
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
puts "❌ Nenhum conteúdo válido detectado (cenários, contexto ou regras)."
|
24
|
+
if erros.any?
|
25
|
+
puts "⚠️ Erros encontrados no arquivo:"
|
26
|
+
erros.each { |e| puts " - #{e}" }
|
28
27
|
return false
|
29
28
|
end
|
30
29
|
|
31
|
-
|
30
|
+
true
|
32
31
|
end
|
33
32
|
end
|
34
33
|
end
|
data/lib/bddgenx/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bddgenx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.18
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Nascimento
|
@@ -20,12 +20,13 @@ extensions: []
|
|
20
20
|
extra_rdoc_files: []
|
21
21
|
files:
|
22
22
|
- README.md
|
23
|
-
-
|
24
|
-
- assets/fonts/DejaVuSansMono-BoldOblique.ttf
|
25
|
-
- assets/fonts/DejaVuSansMono-Oblique.ttf
|
26
|
-
- assets/fonts/DejaVuSansMono.ttf
|
23
|
+
- Rakefile
|
27
24
|
- bin/bddgenx
|
28
25
|
- lib/bddgenx.rb
|
26
|
+
- lib/bddgenx/assets/fonts/DejaVuSansMono-Bold.ttf
|
27
|
+
- lib/bddgenx/assets/fonts/DejaVuSansMono-BoldOblique.ttf
|
28
|
+
- lib/bddgenx/assets/fonts/DejaVuSansMono-Oblique.ttf
|
29
|
+
- lib/bddgenx/assets/fonts/DejaVuSansMono.ttf
|
29
30
|
- lib/bddgenx/backup.rb
|
30
31
|
- lib/bddgenx/cli.rb
|
31
32
|
- lib/bddgenx/generator.rb
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|