brutal 0.4.1 → 1.0.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 +4 -4
- data/README.md +41 -90
- data/bin/brutal +2 -41
- data/lib/brutal.rb +21 -6
- data/lib/brutal/configuration.rb +45 -0
- data/lib/brutal/scaffold.rb +81 -0
- metadata +30 -45
- data/lib/brutal/scaffold_generator/base.rb +0 -30
- data/lib/brutal/scaffold_generator/por.rb +0 -58
- data/lib/brutal/scaffold_generator/rspec.rb +0 -55
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 332c8755656f334781310460780dd233d621ddac83bc7d7dc5f0c555f4d1f955
|
|
4
|
+
data.tar.gz: c49ae44f98432fb3839bcf7c76ac3929bfee3eac82113bc4903e6d288863865e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f015f9cbc3f478c66374b72a3c6e7340863b472eac796833700aa11a501c3361b2519939f0e407308a6800df23bb7ba959ec60d70f5b16acb86f3d30ae0440e2
|
|
7
|
+
data.tar.gz: 90c7d4fef875977bd991a16405675e424524d2478318d33e694d073a47348986a2bb93bc27f9ff5cdfb5d5d48120d334f55733ad0ff6a3880c401ba901c85391
|
data/README.md
CHANGED
|
@@ -8,29 +8,30 @@
|
|
|
8
8
|
|
|
9
9
|
> A _code-first_ approach to automate the writing of unit tests.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Intro
|
|
12
12
|
|
|
13
13
|
[](https://www.youtube.com/embed/cmOt9HhszCI?start=1732&end=1736 "I don't like tests. It's not DRY.")
|
|
14
14
|
|
|
15
15
|
> I don't like tests. It's not DRY.<br/>
|
|
16
16
|
> -- [Matz](https://github.com/matz)
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## Overview
|
|
19
19
|
|
|
20
|
-
Let __Brutal__ shape for you the actual behavior of your code
|
|
20
|
+
Let __Brutal__ shape for you in no time the actual behavior of your code through as many combinations of contexts as needed.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
By delegating to __Brutal__ this repetitive (and redundant) job of writing tests, you'll be able to focus on your core business: writing code.
|
|
22
|
+
By delegating to __Brutal__ the repetitive (and redundant) task of writing tests, you'll be able to focus on your core business: the code itself.
|
|
25
23
|
|
|
26
24
|
## Warning
|
|
27
25
|
|
|
28
|
-
__Brutal__ does not prevent from bugs.
|
|
29
|
-
As a picture of the behavior of the code, generated tests would be wrong if the code is wrong.
|
|
26
|
+
__Brutal__ development process does not prevent from bugs.
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
As a _picture of the behavior of the code_,
|
|
29
|
+
a generated test suite is wrong as long as the code is wrong,
|
|
30
|
+
regardless of whether all true expectations.
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
However, this document becomes relevant when it shows that the code behaves as it is supposed to.
|
|
33
|
+
It is therefore important to read it well.
|
|
34
|
+
This is the price for _Brutal-Driven Development_.
|
|
34
35
|
|
|
35
36
|
## Installation
|
|
36
37
|
|
|
@@ -54,113 +55,63 @@ Just type `brutal` in a Ruby project's folder and watch the magic happen.
|
|
|
54
55
|
|
|
55
56
|
## Usage
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* `header` (optional): Some code to execute before the test suite.
|
|
61
|
-
* `front_object` (required): The front object of the test suite.
|
|
62
|
-
* `subject` (required): The object of each context.
|
|
63
|
-
* `variables` (required): A hash to generate the subject of each context.
|
|
64
|
-
* `actual_values` (required): A list of tests to challenge the subject.
|
|
65
|
-
|
|
66
|
-
### Behavioral integrity
|
|
67
|
-
|
|
68
|
-
In versioned projects,
|
|
69
|
-
the integrity of the behavior of the code could easily be compared by executing `brutal` after changes.
|
|
70
|
-
|
|
71
|
-
Assuming a project is versioned with git,
|
|
72
|
-
if something goes wrong, the `git diff test.rb` command should instantly show changes between the behavior of the previous code and the behavior of the new one.
|
|
73
|
-
|
|
74
|
-
Example of regression from [The Greeter class](https://github.com/fixrb/brutal/raw/master/examples/the_greeter_class/):
|
|
75
|
-
|
|
76
|
-
```diff
|
|
77
|
-
require './greeter'
|
|
78
|
-
|
|
79
|
-
# ------------------------------------------------------------------------------
|
|
80
|
-
|
|
81
|
-
front_object = Greeter
|
|
82
|
-
|
|
83
|
-
# ------------------------------------------------------------------------------
|
|
84
|
-
|
|
85
|
-
actual = front_object.new('world')
|
|
86
|
-
|
|
87
|
-
- raise unless actual.salute == "Hello World!"
|
|
88
|
-
+ raise unless actual.salute == "Hello !"
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### Optional parameters
|
|
58
|
+
The `brutal.yml` file is a manifest you can use to define your __Brutal__ meta-spec.
|
|
59
|
+
It has 4 top-level sections:
|
|
92
60
|
|
|
93
|
-
|
|
61
|
+
* `header` - Specifies the code to execute before generating the test suite.
|
|
62
|
+
* `subject` - Specifies the template of the code to be declined across contexts.
|
|
63
|
+
* `contexts` - Specifies a list of variables to populate the subject's template.
|
|
64
|
+
* `actuals` - Specifies templates to challenge evaluated subjects & get results.
|
|
94
65
|
|
|
95
|
-
|
|
96
|
-
brutal rspec
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Example
|
|
66
|
+
### Getting started
|
|
100
67
|
|
|
101
|
-
|
|
68
|
+
1. Create a `brutal.yml` file in your application's root directory.
|
|
69
|
+
The following example `brutal.yml` defines the shape of a Hello test suite:
|
|
102
70
|
|
|
103
71
|
```yaml
|
|
104
72
|
---
|
|
105
|
-
front_object: |
|
|
106
|
-
"Hello "
|
|
107
|
-
|
|
108
73
|
subject: |
|
|
109
|
-
|
|
74
|
+
"Hello " + "%{string}"
|
|
110
75
|
|
|
111
|
-
|
|
76
|
+
contexts:
|
|
112
77
|
string:
|
|
113
|
-
-
|
|
114
|
-
-
|
|
78
|
+
- Alice
|
|
79
|
+
- Bob
|
|
115
80
|
|
|
116
|
-
|
|
81
|
+
actuals:
|
|
117
82
|
- "%{subject}.to_s"
|
|
118
83
|
- "%{subject}.length"
|
|
119
84
|
```
|
|
120
85
|
|
|
121
|
-
|
|
86
|
+
2. Run the `brutal` command from the same directory.
|
|
87
|
+
|
|
88
|
+
3. Read the generated `test.rb` file in the same directory:
|
|
122
89
|
|
|
123
90
|
```ruby
|
|
124
|
-
|
|
91
|
+
# Brutal test suite
|
|
125
92
|
|
|
126
93
|
# ------------------------------------------------------------------------------
|
|
127
94
|
|
|
128
|
-
actual =
|
|
95
|
+
actual = begin
|
|
96
|
+
"Hello " + "Alice"
|
|
97
|
+
end
|
|
129
98
|
|
|
130
|
-
raise
|
|
131
|
-
raise
|
|
99
|
+
raise if actual.to_s != "Hello Alice"
|
|
100
|
+
raise if actual.length != 11
|
|
132
101
|
|
|
133
102
|
# ------------------------------------------------------------------------------
|
|
134
103
|
|
|
135
|
-
actual =
|
|
104
|
+
actual = begin
|
|
105
|
+
"Hello " + "Bob"
|
|
106
|
+
end
|
|
136
107
|
|
|
137
|
-
raise
|
|
138
|
-
raise
|
|
108
|
+
raise if actual.to_s != "Hello Bob"
|
|
109
|
+
raise if actual.length != 9
|
|
139
110
|
```
|
|
140
111
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
```ruby
|
|
144
|
-
RSpec.describe do
|
|
145
|
-
let(:front_object) { "Hello " }
|
|
146
|
-
|
|
147
|
-
context do
|
|
148
|
-
let(:actual) { front_object + 'Alice' }
|
|
149
|
-
|
|
150
|
-
it { expect(actual.to_s).to eq("Hello Alice") }
|
|
151
|
-
it { expect(actual.length).to eq(11) }
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
context do
|
|
155
|
-
let(:actual) { front_object + 'Bob' }
|
|
156
|
-
|
|
157
|
-
it { expect(actual.to_s).to eq("Hello Bob") }
|
|
158
|
-
it { expect(actual.length).to eq(9) }
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
```
|
|
112
|
+
### More examples
|
|
162
113
|
|
|
163
|
-
|
|
114
|
+
https://github.com/fixrb/brutal/raw/master/examples/
|
|
164
115
|
|
|
165
116
|
## Rake integration example
|
|
166
117
|
|
data/bin/brutal
CHANGED
|
@@ -1,45 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
require_relative File.join('..', 'lib', 'brutal')
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
CONF_PATH = ::File.join(::Dir.pwd, FILE_NAME)
|
|
8
|
-
|
|
9
|
-
abort "File #{CONF_PATH} not found!" unless ::File.exist?(CONF_PATH)
|
|
10
|
-
|
|
11
|
-
conf = ::YAML.load_file(CONF_PATH)
|
|
12
|
-
|
|
13
|
-
header = conf.fetch('header', '').to_s.chomp
|
|
14
|
-
front_object = conf.fetch('front_object')
|
|
15
|
-
subject = conf.fetch('subject')
|
|
16
|
-
variables = conf.fetch('variables', {})
|
|
17
|
-
actual_values = conf.fetch('actual_values')
|
|
18
|
-
|
|
19
|
-
raise ::TypeError unless subject.is_a?(::String)
|
|
20
|
-
raise ::TypeError unless variables.is_a?(::Hash)
|
|
21
|
-
raise ::TypeError unless actual_values.is_a?(::Array)
|
|
22
|
-
|
|
23
|
-
front_object = front_object.chomp if front_object.is_a?(::String)
|
|
24
|
-
|
|
25
|
-
require_relative ::File.join('..', 'lib', 'brutal')
|
|
26
|
-
|
|
27
|
-
scaffold, file_name = case ARGV.fetch(0, nil)
|
|
28
|
-
when nil
|
|
29
|
-
[::Brutal::ScaffoldGenerator::Por, 'test.rb']
|
|
30
|
-
when 'rspec'
|
|
31
|
-
[::Brutal::ScaffoldGenerator::RSpec, 'test_spec.rb']
|
|
32
|
-
else
|
|
33
|
-
abort "#{ARGV[0].inspect} not (yet) supported!"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
eval(header) # rubocop:disable Security/Eval
|
|
37
|
-
|
|
38
|
-
doc = [
|
|
39
|
-
header,
|
|
40
|
-
scaffold.new(front_object, subject, *actual_values, **variables).to_s
|
|
41
|
-
].reject(&:empty?).join("\n\n")
|
|
42
|
-
|
|
43
|
-
file = ::File.open(file_name, 'w')
|
|
44
|
-
file.write(doc)
|
|
45
|
-
file.close
|
|
6
|
+
Brutal.generate!
|
data/lib/brutal.rb
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
%w[
|
|
4
|
+
configuration
|
|
5
|
+
scaffold
|
|
6
|
+
].each { |file_name| require_relative(File.join('brutal', file_name)) }
|
|
7
|
+
|
|
8
|
+
# The Brutal namespace
|
|
6
9
|
module Brutal
|
|
7
|
-
|
|
10
|
+
def self.settings
|
|
11
|
+
Configuration.load!
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.generate
|
|
15
|
+
Scaffold.new(*settings)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.generate!
|
|
19
|
+
file = ::File.open('test.rb', 'w')
|
|
20
|
+
file.write(generate)
|
|
8
21
|
|
|
9
|
-
|
|
10
|
-
|
|
22
|
+
true
|
|
23
|
+
ensure
|
|
24
|
+
file.close
|
|
25
|
+
end
|
|
11
26
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module Brutal
|
|
6
|
+
# Brutal::Configuration
|
|
7
|
+
#
|
|
8
|
+
# @since 1.0.0
|
|
9
|
+
class Configuration
|
|
10
|
+
NAME = '.brutal.yml'
|
|
11
|
+
PATH = ::File.join(::Dir.pwd, NAME).freeze
|
|
12
|
+
|
|
13
|
+
def self.load!
|
|
14
|
+
new.to_a
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.file!
|
|
18
|
+
::YAML.load_file(PATH)
|
|
19
|
+
rescue ::Errno::ENOENT => _e
|
|
20
|
+
abort("File #{PATH} not found!")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
attr_reader(:header, :subject, :contexts, :actuals)
|
|
24
|
+
|
|
25
|
+
# rubocop:disable Metrics/AbcSize
|
|
26
|
+
def initialize
|
|
27
|
+
settings = self.class.file!
|
|
28
|
+
|
|
29
|
+
@header = settings.fetch('header', '# Brutal test suite')
|
|
30
|
+
@subject = settings.fetch('subject', '')
|
|
31
|
+
@contexts = settings.fetch('contexts', {})
|
|
32
|
+
@actuals = settings.fetch('actuals', [])
|
|
33
|
+
|
|
34
|
+
raise ::TypeError, @header.inspect unless @header.is_a?(::String)
|
|
35
|
+
raise ::TypeError, @subject.inspect unless @subject.is_a?(::String)
|
|
36
|
+
raise ::TypeError, @contexts.inspect unless @contexts.is_a?(::Hash)
|
|
37
|
+
raise ::TypeError, @actuals.inspect unless @actuals.is_a?(::Array)
|
|
38
|
+
end
|
|
39
|
+
# rubocop:enable Metrics/AbcSize
|
|
40
|
+
|
|
41
|
+
def to_a
|
|
42
|
+
[header, subject, *actuals, **contexts]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Brutal
|
|
4
|
+
# Brutal::Scaffold
|
|
5
|
+
#
|
|
6
|
+
# @since 1.0.0
|
|
7
|
+
class Scaffold
|
|
8
|
+
attr_reader(:header, :subject, :actuals, :contexts)
|
|
9
|
+
|
|
10
|
+
# Initialize a new scaffold generator
|
|
11
|
+
def initialize(header, subject, *actuals, **contexts)
|
|
12
|
+
warn('Empty subject!') if subject.empty?
|
|
13
|
+
warn('Empty actual values!') if actuals.empty?
|
|
14
|
+
warn('Empty contexts!') if contexts.empty?
|
|
15
|
+
|
|
16
|
+
eval(header) # rubocop:disable Security/Eval
|
|
17
|
+
|
|
18
|
+
@header = header
|
|
19
|
+
@subject = subject
|
|
20
|
+
@actuals = actuals
|
|
21
|
+
@contexts = contexts
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Return a Ruby string that can be evaluated.
|
|
25
|
+
def inspect(object)
|
|
26
|
+
return object.to_s unless object.is_a?(::String)
|
|
27
|
+
|
|
28
|
+
object.strip
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Return a string representation
|
|
32
|
+
#
|
|
33
|
+
# @return [String]
|
|
34
|
+
#
|
|
35
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
36
|
+
def to_s
|
|
37
|
+
header.chomp + "\n" + blank_line + combinations_values.map do |values|
|
|
38
|
+
attributes = context_names.each_with_index.inject({}) do |h, (name, i)|
|
|
39
|
+
h.merge(name.to_sym => inspect(values.fetch(i)))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
actual_str = format(inspect(subject), **attributes)
|
|
43
|
+
|
|
44
|
+
string = <<~CODE
|
|
45
|
+
actual = begin
|
|
46
|
+
#{actual_str.gsub(/^/, ' ')}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
CODE
|
|
50
|
+
|
|
51
|
+
actual = eval(actual_str) # rubocop:disable Security/Eval, Lint/UselessAssignment
|
|
52
|
+
|
|
53
|
+
actuals.each do |actual_value|
|
|
54
|
+
result_str = format(actual_value, subject: 'actual')
|
|
55
|
+
string += "raise if #{result_str} != #{eval(result_str).inspect}\n" # rubocop:disable Security/Eval
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
string
|
|
59
|
+
end.join(blank_line)
|
|
60
|
+
end
|
|
61
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
62
|
+
|
|
63
|
+
def blank_line
|
|
64
|
+
"\n" \
|
|
65
|
+
"# #{('-' * 78)}\n" \
|
|
66
|
+
"\n"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def context_names
|
|
70
|
+
contexts.keys.sort
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def contexts_values
|
|
74
|
+
context_names.map { |context_name| contexts.fetch(context_name) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def combinations_values
|
|
78
|
+
Array(contexts_values[0]).product(*Array(contexts_values[1..-1]))
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
metadata
CHANGED
|
@@ -1,113 +1,99 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: brutal
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cyril Kato
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-01
|
|
11
|
+
date: 2020-03-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '0'
|
|
20
20
|
type: :development
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: rake
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- - "
|
|
31
|
+
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
33
|
+
version: '0'
|
|
34
34
|
type: :development
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
|
-
- - "
|
|
38
|
+
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: rubocop
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0.79'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '0.79'
|
|
40
|
+
version: '0'
|
|
55
41
|
- !ruby/object:Gem::Dependency
|
|
56
42
|
name: rubocop-performance
|
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
|
58
44
|
requirements:
|
|
59
|
-
- - "
|
|
45
|
+
- - ">="
|
|
60
46
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
47
|
+
version: '0'
|
|
62
48
|
type: :development
|
|
63
49
|
prerelease: false
|
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
51
|
requirements:
|
|
66
|
-
- - "
|
|
52
|
+
- - ">="
|
|
67
53
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
54
|
+
version: '0'
|
|
69
55
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
56
|
+
name: rubocop-thread_safety
|
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
|
72
58
|
requirements:
|
|
73
|
-
- - "
|
|
59
|
+
- - ">="
|
|
74
60
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '0
|
|
61
|
+
version: '0'
|
|
76
62
|
type: :development
|
|
77
63
|
prerelease: false
|
|
78
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
65
|
requirements:
|
|
80
|
-
- - "
|
|
66
|
+
- - ">="
|
|
81
67
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '0
|
|
68
|
+
version: '0'
|
|
83
69
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
70
|
+
name: simplecov
|
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
|
86
72
|
requirements:
|
|
87
|
-
- - "
|
|
73
|
+
- - ">="
|
|
88
74
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '
|
|
75
|
+
version: '0'
|
|
90
76
|
type: :development
|
|
91
77
|
prerelease: false
|
|
92
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
79
|
requirements:
|
|
94
|
-
- - "
|
|
80
|
+
- - ">="
|
|
95
81
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '
|
|
82
|
+
version: '0'
|
|
97
83
|
- !ruby/object:Gem::Dependency
|
|
98
84
|
name: yard
|
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
|
100
86
|
requirements:
|
|
101
|
-
- - "
|
|
87
|
+
- - ">="
|
|
102
88
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '0
|
|
89
|
+
version: '0'
|
|
104
90
|
type: :development
|
|
105
91
|
prerelease: false
|
|
106
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
93
|
requirements:
|
|
108
|
-
- - "
|
|
94
|
+
- - ">="
|
|
109
95
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '0
|
|
96
|
+
version: '0'
|
|
111
97
|
description: A code-first approach to automate the writing of unit tests.
|
|
112
98
|
email: contact@cyril.email
|
|
113
99
|
executables:
|
|
@@ -119,9 +105,8 @@ files:
|
|
|
119
105
|
- README.md
|
|
120
106
|
- bin/brutal
|
|
121
107
|
- lib/brutal.rb
|
|
122
|
-
- lib/brutal/
|
|
123
|
-
- lib/brutal/
|
|
124
|
-
- lib/brutal/scaffold_generator/rspec.rb
|
|
108
|
+
- lib/brutal/configuration.rb
|
|
109
|
+
- lib/brutal/scaffold.rb
|
|
125
110
|
homepage: https://github.com/fixrb/brutal
|
|
126
111
|
licenses:
|
|
127
112
|
- MIT
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Brutal
|
|
4
|
-
module ScaffoldGenerator
|
|
5
|
-
# Base class
|
|
6
|
-
class Base
|
|
7
|
-
# @api private
|
|
8
|
-
attr_reader :front_object_str, :front_object, :subject, :actual_values, :variables
|
|
9
|
-
|
|
10
|
-
# Initialize a new scaffold generator
|
|
11
|
-
def initialize(front_object, subject, *actual_values, **variables)
|
|
12
|
-
raise 'Empty actual values!' if actual_values.empty?
|
|
13
|
-
raise 'Empty variables!' if variables.empty?
|
|
14
|
-
|
|
15
|
-
@front_object_str = inspect(front_object)
|
|
16
|
-
@front_object = eval(@front_object_str) # rubocop:disable Security/Eval
|
|
17
|
-
@subject = subject
|
|
18
|
-
@actual_values = actual_values
|
|
19
|
-
@variables = variables
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Return a Ruby string that can be evaluated.
|
|
23
|
-
def inspect(object)
|
|
24
|
-
return object.to_s unless object.is_a?(::String)
|
|
25
|
-
|
|
26
|
-
object.strip
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'base'
|
|
4
|
-
|
|
5
|
-
module Brutal
|
|
6
|
-
module ScaffoldGenerator
|
|
7
|
-
# Plain Old Ruby.
|
|
8
|
-
#
|
|
9
|
-
# @api private
|
|
10
|
-
#
|
|
11
|
-
class Por < Base
|
|
12
|
-
# Return a string representation
|
|
13
|
-
#
|
|
14
|
-
# @return [String]
|
|
15
|
-
#
|
|
16
|
-
# @api public
|
|
17
|
-
def to_s
|
|
18
|
-
names = variables.keys.sort
|
|
19
|
-
values_arr = names.map { |name| variables.fetch(name) }
|
|
20
|
-
|
|
21
|
-
test_params = Array(values_arr[0]).product(*Array(values_arr[1..-1]))
|
|
22
|
-
|
|
23
|
-
blank_line.gsub(/\A\n/, '') +
|
|
24
|
-
"front_object = #{front_object_str}\n" +
|
|
25
|
-
blank_line +
|
|
26
|
-
test_params.map do |values|
|
|
27
|
-
attributes = names.each_with_index.inject(front_object: 'front_object') do |h, (name, i)|
|
|
28
|
-
h.merge(name.to_sym => inspect(values.fetch(i)))
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
actual_str = format(inspect(subject), **attributes)
|
|
32
|
-
|
|
33
|
-
string = <<~CODE
|
|
34
|
-
actual = begin
|
|
35
|
-
#{actual_str.gsub(/^/, ' ')}
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
CODE
|
|
39
|
-
|
|
40
|
-
actual = eval(actual_str) # rubocop:disable Security/Eval, Lint/UselessAssignment
|
|
41
|
-
|
|
42
|
-
actual_values.each do |actual_value|
|
|
43
|
-
result_str = format(actual_value, subject: 'actual')
|
|
44
|
-
string += "raise unless #{result_str} == #{eval(result_str).inspect}\n" # rubocop:disable Security/Eval
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
string
|
|
48
|
-
end.join(blank_line)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def blank_line
|
|
52
|
-
"\n" \
|
|
53
|
-
"# #{('-' * 78)}\n" \
|
|
54
|
-
"\n"
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'base'
|
|
4
|
-
|
|
5
|
-
module Brutal
|
|
6
|
-
module ScaffoldGenerator
|
|
7
|
-
# Plain Old Ruby.
|
|
8
|
-
#
|
|
9
|
-
# @api private
|
|
10
|
-
#
|
|
11
|
-
class RSpec < Base
|
|
12
|
-
# Return a string representation
|
|
13
|
-
#
|
|
14
|
-
# @return [String]
|
|
15
|
-
#
|
|
16
|
-
# @api public
|
|
17
|
-
def to_s
|
|
18
|
-
names = variables.keys.sort
|
|
19
|
-
values_arr = names.map { |name| variables.fetch(name) }
|
|
20
|
-
|
|
21
|
-
test_params = Array(values_arr[0]).product(*Array(values_arr[1..-1]))
|
|
22
|
-
|
|
23
|
-
"RSpec.describe do\n" \
|
|
24
|
-
" let(:front_object) { #{front_object_str} }\n" \
|
|
25
|
-
"\n" +
|
|
26
|
-
test_params.map do |values|
|
|
27
|
-
string = " context do\n"
|
|
28
|
-
|
|
29
|
-
attributes = names.each_with_index.inject(front_object: 'front_object') do |h, (name, i)|
|
|
30
|
-
h.merge(name.to_sym => inspect(values.fetch(i)))
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
actual_str = format(inspect(subject), **attributes)
|
|
34
|
-
|
|
35
|
-
string += " let(:actual) { #{actual_str} }\n\n"
|
|
36
|
-
actual = eval(actual_str) # rubocop:disable Security/Eval, Lint/UselessAssignment
|
|
37
|
-
|
|
38
|
-
actual_values.each do |actual_value|
|
|
39
|
-
result_str = format(actual_value, subject: 'actual')
|
|
40
|
-
string += " it { expect(#{result_str}).to eq(#{eval(result_str).inspect}) }\n" # rubocop:disable Security/Eval
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
string += " end\n"
|
|
44
|
-
|
|
45
|
-
string
|
|
46
|
-
end.join(blank_line) +
|
|
47
|
-
"end\n"
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def blank_line
|
|
51
|
-
"\n"
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|