bddgenx 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ef4f9af959d68062c22da0b3cb716f9ac06ad41f10118cca1be47421d1bf7418
4
+ data.tar.gz: ad9885cb9a2baa14598a0278d3b7ff809bda5759058ef4828161e12c31485848
5
+ SHA512:
6
+ metadata.gz: 91fbab9be46cabe7a27f15f8c47635c81d33801958373bceb40bf6ce0d925322f7889a5e79de9fc0dd7bb347971636639d99be7377afd05c15ef1d21cd8780cc
7
+ data.tar.gz: 71cb4f733fcf27a7d5c1fb8f0c1690f0322178a6b9e8c6813ecd91ebf302afe63eed0fc5f7bd4a4eb55d2b77b6d3e73df71b91c8e96e6b03f05c5719a4a7772f
data/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # 🧪 Gerador de BDD Automático em Ruby
2
+
3
+ Este projeto gera arquivos `.feature` (Gherkin) e `steps.rb` automaticamente a partir de arquivos `.txt` com histórias de usuário, seguindo padrões ISTQB, parametrização com `Examples` e integração com pipelines.
4
+
5
+ ---
6
+
7
+ ## 📂 Estrutura do Projeto
8
+ ```txt
9
+ bdd_generator/
10
+ ├── input/ # Arquivos .txt com histórias de usuário
11
+ ├── features/ # Arquivos .feature gerados
12
+ ├── steps/ # Arquivos com step definitions
13
+ ├── output/
14
+ │ └── rastreabilidade.csv
15
+ ├── backup/ # Versões antigas de features sobrescritas
16
+ ├── lib/
17
+ │ ├── parser.rb
18
+ │ ├── generator.rb
19
+ │ ├── validator.rb
20
+ │ ├── steps_generator.rb
21
+ │ ├── tracer.rb
22
+ │ ├── backup.rb
23
+ ├── main.rb
24
+ ├── Rakefile
25
+ └── README.md
26
+ ```
27
+ ## ▶️ Como Executar
28
+
29
+ ### 🔧 Requisitos
30
+ - Ruby 3.x
31
+ - `bundle install` (caso use gems como `prawn` ou `jira-ruby`)
32
+
33
+ ### 🏁 Comando direto:
34
+
35
+ ```bash
36
+ ruby main.rb
37
+ ```
38
+
39
+ 🧱 Com Rake:
40
+ ```bash
41
+ rake bddgen:gerar
42
+ ```
43
+
44
+ 📥 Como Escrever um .txt de Entrada
45
+ ```txt
46
+ # language: pt
47
+ Como um cliente do e-commerce
48
+ Quero adicionar produtos ao carrinho
49
+ Para finalizar minha compra com praticidade
50
+
51
+ [CONTEXT]
52
+ Dado que estou logado na plataforma
53
+ E tenho produtos disponíveis
54
+
55
+ [REGRA]
56
+ O carrinho não deve permitir produtos fora de estoque
57
+ E o valor total deve refletir o desconto promocional
58
+
59
+ [SUCCESS]
60
+ Quando adiciono um produto ao carrinho
61
+ Então ele aparece na listagem do carrinho
62
+
63
+ [FAILURE]
64
+ Quando tento adicionar um produto esgotado
65
+ Então recebo uma mensagem de "produto indisponível"
66
+
67
+ [EXAMPLES]
68
+ | produto | quantidade | total esperado |
69
+ | Camiseta Azul | 2 | 100 |
70
+ | Tênis Branco | 1 | 250 |
71
+
72
+ [SUCCESS]
73
+ Quando adiciono "<produto>" com quantidade <quantidade>
74
+ Então vejo o total <total esperado>
75
+ ```
76
+ ✅ Blocos Suportados
77
+ [CONTEXT] – contexto comum
78
+
79
+ [SUCCESS] – cenário positivo
80
+
81
+ [FAILURE] – cenário negativo
82
+
83
+ [ERROR], [EXCEPTION], [PERFORMANCE], etc.
84
+
85
+ [REGRA] ou [RULE] – regras de negócio
86
+
87
+ [EXAMPLES] – tabela de dados para Scenario Outline
88
+
89
+ 🧠 Saída esperada (feature)
90
+ ```gherkin
91
+ # language: pt
92
+ Funcionalidade: adicionar produtos ao carrinho
93
+
94
+ Como um cliente do e-commerce
95
+ Quero adicionar produtos ao carrinho
96
+ Para finalizar minha compra com praticidade
97
+
98
+ Regra: O carrinho não deve permitir produtos fora de estoque
99
+ E o valor total deve refletir o desconto promocional
100
+
101
+ Contexto:
102
+ Dado que estou logado na plataforma
103
+ E tenho produtos disponíveis
104
+
105
+ @success
106
+ Cenário: Teste Positivo - adiciono um produto ao carrinho - ele aparece na listagem do carrinho
107
+ Quando adiciono um produto ao carrinho
108
+ Então ele aparece na listagem do carrinho
109
+
110
+ Esquema do Cenário: Gerado a partir de dados de exemplo
111
+ Quando adiciono "<produto>" com quantidade <quantidade>
112
+ Então vejo o total <total esperado>
113
+
114
+ Exemplos:
115
+ | produto | quantidade | total esperado |
116
+ | Camiseta Azul | 2 | 100 |
117
+ | Tênis Branco | 1 | 250 |
118
+ ```
119
+
120
+ 🧩 Step Definitions geradas
121
+ ```ruby
122
+ Quando('adiciono "<produto>" com quantidade <quantidade>') do |produto, quantidade|
123
+ pending 'Implementar passo: adiciono "<produto>" com quantidade <quantidade>'
124
+ end
125
+
126
+ Então('vejo o total <total esperado>') do |total_esperado|
127
+ pending 'Implementar passo: vejo o total <total esperado>'
128
+ end
129
+ ```
130
+ 🧾 Rastreabilidade
131
+ - Gera automaticamente um CSV em output/rastreabilidade.csv com:
132
+ - Nome do cenário
133
+ - Tipo (SUCCESS, FAILURE, etc.)
134
+ - Caminho do .feature
135
+ - Origem do .txt
136
+
137
+ 🔄 Backup
138
+ Toda vez que um .feature existente for sobrescrito, a versão anterior é salva em:
139
+ ```
140
+ backup/
141
+ ```
142
+ ✅ Execução em CI/CD (GitHub Actions)
143
+ ```yaml
144
+ jobs:
145
+ gerar_bdd:
146
+ runs-on: ubuntu-latest
147
+ steps:
148
+ - uses: actions/checkout@v3
149
+ - uses: ruby/setup-ruby@v1
150
+ with:
151
+ ruby-version: '3.2'
152
+ - run: ruby main.rb
153
+ ```
154
+
155
+ 👨‍💻 Autor
156
+ David Nascimento – Projeto de automação BDD com Ruby – 2025
157
+ ```yaml
158
+ ---
159
+
160
+ Pronto para copiar, colar ou subir no GitHub como `README.md`. Deseja que eu prepare um `.zip` com tudo funcionando como entrega final?
161
+ ```
data/bin/bddgenx ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/bddgen/cli"
3
+
4
+ Bddgenx::CLI.start
@@ -0,0 +1,16 @@
1
+ require 'fileutils'
2
+ require 'time'
3
+
4
+ module Backup
5
+ def self.salvar_versao_antiga(caminho)
6
+ return unless File.exist?(caminho)
7
+
8
+ FileUtils.mkdir_p("backup")
9
+ base = File.basename(caminho, ".feature")
10
+ timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
11
+ destino = "backup/#{base}_#{timestamp}.feature"
12
+
13
+ FileUtils.cp(caminho, destino)
14
+ puts "📦 Backup criado: #{destino}"
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ module CLI
2
+ def self.selecionar_arquivos_txt(diretorio)
3
+ arquivos = Dir.glob("#{diretorio}/*.txt")
4
+
5
+ if arquivos.empty?
6
+ puts "❌ Nenhum arquivo .txt encontrado no diretório '#{diretorio}'"
7
+ exit
8
+ end
9
+
10
+ arquivos
11
+
12
+ puts "📂 Arquivos disponíveis em '#{diretorio}':"
13
+ arquivos.each_with_index do |arquivo, i|
14
+ puts " #{i + 1}. #{File.basename(arquivo)}"
15
+ end
16
+
17
+ print "\nDigite os números dos arquivos que deseja processar (ex: 1,2,3 ou 'todos'): "
18
+ entrada = gets.chomp
19
+
20
+ selecionados = if entrada.downcase == 'todos'
21
+ arquivos
22
+ else
23
+ indices = entrada.split(',').map { |n| n.strip.to_i - 1 }
24
+ indices.map { |i| arquivos[i] }.compact
25
+ end
26
+
27
+ if selecionados.empty?
28
+ puts "❌ Nenhum arquivo válido selecionado."
29
+ exit
30
+ end
31
+
32
+ selecionados
33
+ end
34
+ end
@@ -0,0 +1,123 @@
1
+ require 'fileutils'
2
+
3
+ module Generator
4
+ TIPOS_ISTQB = {
5
+ "SUCCESS" => "Teste Positivo",
6
+ "FAILURE" => "Teste Negativo",
7
+ "ERROR" => "Teste de Erro",
8
+ "EXCEPTION" => "Teste de Exceção",
9
+ "VALIDATION" => "Teste de Validação",
10
+ "PERMISSION" => "Teste de Permissão",
11
+ "EDGE_CASE" => "Teste de Limite",
12
+ "PERFORMANCE" => "Teste de Desempenho"
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'
31
+ }
32
+
33
+ nome_base = historia[:quero].gsub(/[^a-zA-Z0-9]/, '_').downcase
34
+ caminho = "features/#{nome_base}.feature"
35
+
36
+ conteudo = <<~HEADER
37
+ # language: #{idioma}
38
+ Funcionalidade: #{historia[:quero].sub(/^Quero/, '').strip}
39
+
40
+ #{historia[:como]}
41
+ #{historia[:quero]}
42
+ #{historia[:para]}
43
+
44
+ HEADER
45
+
46
+ # Regras
47
+ if historia[:regras]&.any?
48
+ conteudo += " #{palavras[:regra]}: #{historia[:regras].first}\n"
49
+ historia[:regras][1..].each do |linha|
50
+ conteudo += " #{linha}\n"
51
+ end
52
+ conteudo += "\n"
53
+ end
54
+
55
+
56
+ # Contexto
57
+ if historia[:blocos]["CONTEXT"]&.any?
58
+ conteudo += " #{palavras[:contexto]}:\n"
59
+ historia[:blocos]["CONTEXT"].each { |p| conteudo += " #{p}\n" }
60
+ conteudo += "\n"
61
+ end
62
+
63
+ # Cenários
64
+ TIPOS_CENARIO.each do |tipo|
65
+ passos = historia[:blocos][tipo]
66
+ next unless passos&.any?
67
+
68
+ nome_teste = TIPOS_ISTQB[tipo] || palavras[:cenario]
69
+ contexto = passos.first&.gsub(/^(Dado que|Given|Quando|When|Então|Then|E|And)/, '')&.strip || "Condição"
70
+ resultado = passos.last&.gsub(/^(Então|Then|E|And)/, '')&.strip || "Resultado"
71
+ nome_cenario = "#{nome_teste} - #{contexto} - #{resultado}"
72
+
73
+ conteudo += " @#{tipo.downcase}\n"
74
+ conteudo += " #{palavras[:cenario]}: #{nome_cenario}\n"
75
+ passos.each { |p| conteudo += " #{p}\n" }
76
+ conteudo += "\n"
77
+ end
78
+
79
+ # Esquema do Cenário com Exemplos
80
+ if historia[:blocos]["EXAMPLES"]&.any?
81
+ exemplos_bruto = historia[:blocos]["EXAMPLES"]
82
+ cabecalho = exemplos_bruto.first.gsub('|', '').split.map(&:strip)
83
+ linhas = exemplos_bruto[1..]
84
+
85
+ # Procura qualquer cenário que use parâmetros
86
+ tipo_cenario = historia[:blocos].keys.find { |k| historia[:blocos][k].any? { |l| l.include?('<') } }
87
+ passos_exemplo = tipo_cenario ? historia[:blocos][tipo_cenario].select { |l| l.include?('<') } : []
88
+
89
+ if idioma == 'en'
90
+ conteudo += " Scenario Outline: Generated scenario with data\n"
91
+ else
92
+ conteudo += " Esquema do Cenário: Gerado a partir de dados de exemplo\n"
93
+ end
94
+
95
+ passos_exemplo.each do |passo|
96
+ conteudo += " #{passo}\n"
97
+ end
98
+
99
+ conteudo += "\n"
100
+ conteudo += idioma == 'en' ? " Examples:\n" : " Exemplos:\n"
101
+ conteudo += " #{exemplos_bruto.first}\n"
102
+ linhas.each { |linha| conteudo += " #{linha}\n" }
103
+ end
104
+
105
+
106
+
107
+
108
+ [caminho, conteudo]
109
+ end
110
+
111
+ # Salva o arquivo .feature gerado
112
+ # Retorna true se o arquivo foi salvo com sucesso, false caso contrário
113
+ def self.salvar_feature(caminho, conteudo)
114
+ if conteudo.strip.empty?
115
+ puts "⚠️ Nenhum conteúdo gerado para: #{caminho} (ignorado)"
116
+ return false
117
+ end
118
+
119
+ File.write(caminho, conteudo)
120
+ puts "✅ Arquivo .feature gerado: #{caminho}"
121
+ true
122
+ end
123
+ end
@@ -0,0 +1,32 @@
1
+ require 'jira-ruby'
2
+
3
+ module Bddgen
4
+ module Integrations
5
+ class Jira
6
+ def initialize(options = {})
7
+ @client = JIRA::Client.new(
8
+ username: options[:username],
9
+ password: options[:api_token],
10
+ site: options[:site],
11
+ context_path: '',
12
+ auth_type: :basic
13
+ )
14
+ @project_key = options[:project_key]
15
+ end
16
+
17
+ def enviar_cenario(titulo, descricao)
18
+ issue = {
19
+ fields: {
20
+ project: { key: @project_key },
21
+ summary: titulo,
22
+ description: descricao,
23
+ issuetype: { name: "Task" }
24
+ }
25
+ }
26
+
27
+ @client.Issue.build.save(issue)
28
+ puts "✅ Cenário enviado para Jira: #{titulo}"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ require 'xmlrpc/client'
2
+
3
+ module Bddgen
4
+ module Integrations
5
+ class TestLink
6
+ def initialize(api_key, url)
7
+ @server = XMLRPC::Client.new2(url)
8
+ @key = api_key
9
+ end
10
+
11
+ def criar_caso_teste(plan_id, titulo, passos)
12
+ steps_formated = passos.map.with_index(1) do |step, i|
13
+ {
14
+ step_number: i,
15
+ actions: step,
16
+ expected_results: '',
17
+ execution_type: 1
18
+ }
19
+ end
20
+
21
+ params = {
22
+ devKey: @key,
23
+ testprojectid: 1,
24
+ testsuiteid: plan_id,
25
+ testcasename: titulo,
26
+ steps: steps_formated
27
+ }
28
+
29
+ response = @server.call('tl.createTestCase', params)
30
+ puts "✅ Teste enviado ao TestLink: #{titulo}"
31
+ response
32
+ rescue => e
33
+ puts "❌ Erro ao criar caso no TestLink: #{e.message}"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,52 @@
1
+ module Parser
2
+ TIPOS_BLOCOS = %w[
3
+ CONTEXT SUCCESS FAILURE ERROR EXCEPTION
4
+ VALIDATION PERMISSION EDGE_CASE PERFORMANCE
5
+ EXAMPLES REGRA RULE
6
+ ].freeze
7
+
8
+ def self.ler_historia(caminho_arquivo)
9
+ linhas = File.readlines(caminho_arquivo, encoding: 'utf-8').map(&:strip).reject(&:empty?)
10
+
11
+ # Detecta idioma
12
+ idioma = linhas.first.downcase.include?('# lang: en') ? 'en' : 'pt'
13
+ linhas.shift if linhas.first.downcase.start_with?('#')
14
+
15
+ # Ignora linhas que sejam blocos ou comentários até encontrar Como/Quero/Para
16
+ cabecalho = []
17
+ until linhas.empty?
18
+ linha = linhas.shift
19
+ break if linha.start_with?("Como", "As") # início da história em pt ou en
20
+ end
21
+ como = linha
22
+ quero = linhas.shift
23
+ para = linhas.shift
24
+
25
+ historia = {
26
+ como: como,
27
+ quero: quero,
28
+ para: para,
29
+ blocos: Hash.new { |h, k| h[k] = [] },
30
+ regras: [],
31
+ arquivo_origem: caminho_arquivo,
32
+ idioma: idioma
33
+ }
34
+
35
+ tipo_atual = nil
36
+
37
+ linhas.each do |linha|
38
+ if linha.match?(/^\[(#{TIPOS_BLOCOS.join('|')})\]$/)
39
+ tipo_atual = linha.gsub(/[\[\]]/, '')
40
+ next
41
+ end
42
+
43
+ if %w[REGRA RULE].include?(tipo_atual)
44
+ historia[:regras] << linha
45
+ else
46
+ historia[:blocos][tipo_atual] << linha if tipo_atual
47
+ end
48
+ end
49
+
50
+ historia
51
+ end
52
+ end
@@ -0,0 +1,29 @@
1
+ require 'prawn'
2
+ require 'fileutils'
3
+
4
+ module Bddgen
5
+ class PDFExporter
6
+ def self.exportar_todos
7
+ FileUtils.mkdir_p('pdf')
8
+
9
+ Dir.glob('features/*.feature').each do |feature_file|
10
+ nome = File.basename(feature_file, '.feature')
11
+ destino = "pdf/#{nome}.pdf"
12
+
13
+ exportar_arquivo(feature_file, destino)
14
+ puts "📄 PDF gerado: #{destino}"
15
+ end
16
+ end
17
+
18
+ def self.exportar_arquivo(origem, destino)
19
+ conteudo = File.read(origem, encoding: 'utf-8')
20
+
21
+ Prawn::Document.generate(destino) do |pdf|
22
+ pdf.font_size 10
23
+ pdf.text "Arquivo: #{File.basename(origem)}", style: :bold, size: 14
24
+ pdf.move_down 10
25
+ pdf.text conteudo, size: 10
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,107 @@
1
+ require 'fileutils'
2
+
3
+ module StepsGenerator
4
+ PADROES = {
5
+ 'pt' => %w[Dado Quando Então E],
6
+ 'en' => %w[Given When Then And]
7
+ }
8
+
9
+ TIPOS_BLOCOS = %w[
10
+ CONTEXT SUCCESS FAILURE ERROR EXCEPTION
11
+ VALIDATION PERMISSION EDGE_CASE PERFORMANCE
12
+ EXAMPLES REGRA RULE
13
+ ]
14
+
15
+ def self.gerar_passos(historia, nome_arquivo_feature)
16
+ idioma = historia[:idioma] || 'pt'
17
+ conectores = PADROES[idioma]
18
+ passos_gerados = []
19
+
20
+ # Detectar parâmetros e tipos a partir do bloco EXAMPLES
21
+ exemplos = extrair_exemplos(historia[:blocos]["EXAMPLES"])
22
+
23
+ TIPOS_BLOCOS.each do |tipo|
24
+ blocos = tipo == "REGRA" || tipo == "RULE" ? historia[:regras] : historia[:blocos][tipo]
25
+ next unless blocos.is_a?(Array)
26
+
27
+ blocos.each do |linha|
28
+ next if tipo == "EXAMPLES" && linha.strip.start_with?("|") # ignora linhas de tabela
29
+
30
+ conector = conectores.find { |c| linha.strip.start_with?(c) }
31
+ next unless conector
32
+
33
+ corpo = linha.strip.sub(/^#{conector}/, '').strip
34
+ corpo_parametrizado = substituir_parametros(corpo, exemplos)
35
+
36
+ chave = { conector: conector, raw: corpo, param: corpo_parametrizado }
37
+ passos_gerados << chave unless passos_gerados.any? { |p| p[:param] == corpo_parametrizado }
38
+ end
39
+ end
40
+
41
+ if passos_gerados.empty?
42
+ puts "⚠️ Nenhum passo detectado em: #{nome_arquivo_feature} (arquivo não gerado)"
43
+ return false
44
+ end
45
+
46
+ nome_base = File.basename(nome_arquivo_feature, '.feature')
47
+ caminho = "steps/#{nome_base}_steps.rb"
48
+ FileUtils.mkdir_p(File.dirname(caminho))
49
+
50
+ comentario = "# Step definitions para #{File.basename(nome_arquivo_feature)}"
51
+ comentario += idioma == 'en' ? " (English)" : " (Português)"
52
+ conteudo = "#{comentario}\n\n"
53
+
54
+ passos_gerados.each do |passo|
55
+ # Extrai nomes dos parâmetros entre < >
56
+ parametros = passo[:raw].scan(/<([^>]+)>/).flatten.map(&:strip)
57
+ param_list = parametros.map { |p| p.gsub(' ', '_') }.join(', ')
58
+
59
+ conteudo += <<~STEP
60
+ #{passo[:conector]}('#{passo[:param]}') do#{param_list.empty? ? '' : " |#{param_list}|" }
61
+ pending '#{idioma == 'en' ? 'Implement step' : 'Implementar passo'}: #{passo[:raw]}'
62
+ end
63
+
64
+ STEP
65
+ end
66
+
67
+ FileUtils.mkdir_p("steps")
68
+ File.write(caminho, conteudo)
69
+ puts "✅ Step definitions gerados: #{caminho}"
70
+ true
71
+ end
72
+
73
+ def self.substituir_parametros(texto, exemplos)
74
+ texto.gsub(/<([^>]+)>/) do |_match|
75
+ nome = $1.strip
76
+ tipo = detectar_tipo_param(nome, exemplos)
77
+ "{#{tipo}}"
78
+ end
79
+ end
80
+
81
+ def self.detectar_tipo_param(nome_coluna, exemplos)
82
+ return 'string' unless exemplos && exemplos[:cabecalho].include?(nome_coluna)
83
+
84
+ idx = exemplos[:cabecalho].index(nome_coluna)
85
+ valores = exemplos[:linhas].map { |l| l[idx] }
86
+
87
+ if valores.all? { |v| v.match?(/^\d+$/) }
88
+ 'int'
89
+ elsif valores.all? { |v| v.match?(/^\d+\.\d+$/) }
90
+ 'float'
91
+ else
92
+ 'string'
93
+ end
94
+ end
95
+
96
+ def self.extrair_exemplos(bloco)
97
+ return nil unless bloco&.any?
98
+
99
+ linhas = bloco.map(&:strip)
100
+ cabecalho = linhas.first.gsub('|', '').split.map(&:strip)
101
+ dados = linhas[1..].map { |linha| linha.gsub('|', '').split.map(&:strip) }
102
+
103
+ { cabecalho: cabecalho, linhas: dados }
104
+ end
105
+ end
106
+
107
+
@@ -0,0 +1,32 @@
1
+ require 'csv'
2
+ require 'fileutils'
3
+
4
+ module Tracer
5
+ ARQUIVO = 'output/rastreabilidade.csv'
6
+
7
+ def self.adicionar_entrada(historia, caminho_arquivo)
8
+ FileUtils.mkdir_p("output")
9
+
10
+ CSV.open(ARQUIVO, File.exist?(ARQUIVO) ? 'a' : 'w', col_sep: ';') do |csv|
11
+ unless File.exist?(ARQUIVO)
12
+ csv << ["Funcionalidade", "Tipo de Teste", "Nome do Cenário", "Arquivo .feature"]
13
+ end
14
+
15
+ historia[:blocos].each do |tipo, passos|
16
+ next if tipo == "CONTEXT" || tipo == "EXAMPLES" || passos.empty?
17
+
18
+ tipo_istqb = tipo.capitalize.gsub('_', ' ').capitalize
19
+ contexto = passos.first&.gsub(/^(Dado que|Quando|Então|E)/, '')&.strip || "Condição"
20
+ resultado = passos.last&.gsub(/^(Então|E)/, '')&.strip || "Resultado"
21
+ nome_cenario = "#{tipo_istqb} - #{contexto} - #{resultado}"
22
+
23
+ csv << [
24
+ historia[:quero].sub(/^Quero/, '').strip,
25
+ tipo_istqb,
26
+ nome_cenario,
27
+ caminho_arquivo
28
+ ]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ module Validator
2
+ TIPOS_CENARIO = %w[
3
+ SUCCESS
4
+ FAILURE
5
+ ERROR
6
+ EXCEPTION
7
+ VALIDATION
8
+ PERMISSION
9
+ EDGE_CASE
10
+ PERFORMANCE
11
+ ]
12
+
13
+ def self.validar(historia)
14
+ valido = true
15
+
16
+ if historia[:como].to_s.strip.empty? ||
17
+ historia[:quero].to_s.strip.empty? ||
18
+ historia[:para].to_s.strip.empty?
19
+ puts "❌ História incompleta: 'Como', 'Quero' ou 'Para' está faltando."
20
+ valido = false
21
+ end
22
+
23
+ cenarios_presentes = historia[:blocos].keys & TIPOS_CENARIO
24
+ valido ||= historia[:blocos]["CONTEXT"]&.any? || historia[:regras]&.any?
25
+ if cenarios_presentes.empty? && !valido
26
+ puts "❌ Nenhum conteúdo válido detectado (cenários, contexto ou regras)."
27
+ return false
28
+ end
29
+
30
+ valido
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module Bddgenx
2
+ VERSION = "0.1.0"
3
+ end
data/lib/bddgenx.rb ADDED
@@ -0,0 +1,47 @@
1
+ require_relative "bddgen/version"
2
+ require_relative "bddgen/cli"
3
+ require_relative "bddgen/parser"
4
+ require_relative "bddgen/validator"
5
+ require_relative "bddgen/generator"
6
+ require_relative "bddgen/steps_generator"
7
+ require_relative "bddgen/tracer"
8
+ require_relative "bddgen/backup"
9
+ require_relative 'bddgen/pdf_exporter'
10
+
11
+ cont_total = 0
12
+ cont_features = 0
13
+ cont_steps = 0
14
+ cont_ignorados = 0
15
+
16
+ # Exibe menu inicial e pergunta quais arquivos processar
17
+ arquivos = CLI.selecionar_arquivos_txt('input')
18
+
19
+ arquivos.each do |arquivo_path|
20
+ puts "\n🔍 Processando: #{arquivo_path}"
21
+
22
+ historia = Parser.ler_historia(arquivo_path)
23
+
24
+ unless Validator.validar(historia)
25
+ cont_ignorados += 1
26
+ puts "❌ Arquivo inválido: #{arquivo_path}"
27
+ next
28
+ end
29
+
30
+ nome_feature, conteudo_feature = Generator.gerar_feature(historia)
31
+
32
+ Backup.salvar_versao_antiga(nome_feature)
33
+ cont_features += 1 if Generator.salvar_feature(nome_feature, conteudo_feature)
34
+ cont_steps += 1 if StepsGenerator.gerar_passos(historia, nome_feature)
35
+
36
+
37
+ Tracer.adicionar_entrada(historia, nome_feature)
38
+ end
39
+
40
+ puts "\n✅ Processamento finalizado. Arquivos gerados em: features/, steps/, output/"
41
+ puts "🔄 Versões antigas salvas em: backup/"
42
+
43
+ puts "\n✅ Processamento finalizado:"
44
+ puts "- Arquivos processados: #{cont_total}"
45
+ puts "- Features geradas: #{cont_features}"
46
+ puts "- Steps gerados: #{cont_steps}"
47
+ puts "- Ignorados: #{cont_ignorados}"
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bddgenx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - David Nascimento
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-05-10 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Transforma arquivos .txt com histórias em arquivos .feature, com steps,
14
+ rastreabilidade e integração com CI/CD.
15
+ email:
16
+ - halison700@gmail.com
17
+ executables:
18
+ - bddgenx
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - README.md
23
+ - bin/bddgenx
24
+ - lib/bddgenx.rb
25
+ - lib/bddgenx/backup.rb
26
+ - lib/bddgenx/cli.rb
27
+ - lib/bddgenx/generator.rb
28
+ - lib/bddgenx/integrations/jira.rb
29
+ - lib/bddgenx/integrations/testlink.rb
30
+ - lib/bddgenx/parser.rb
31
+ - lib/bddgenx/pdf_exporter.rb
32
+ - lib/bddgenx/steps_generator.rb
33
+ - lib/bddgenx/tracer.rb
34
+ - lib/bddgenx/validator.rb
35
+ - lib/bddgenx/version.rb
36
+ homepage: https://github.com/David-Nascimento/bdd-generation
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 3.x
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubygems_version: 3.3.27
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: Geração automática de BDD a partir de histórias de usuário
59
+ test_files: []