fix 0.18.2 → 0.19
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.md +2 -2
- data/README.md +384 -84
- data/lib/fix/builder.rb +101 -0
- data/lib/fix/doc.rb +59 -0
- data/lib/fix/dsl.rb +139 -0
- data/lib/fix/error/invalid_specification_name.rb +12 -0
- data/lib/fix/error/missing_specification_block.rb +14 -0
- data/lib/fix/error/specification_not_found.rb +12 -0
- data/lib/fix/matcher.rb +76 -0
- data/lib/fix/requirement.rb +119 -0
- data/lib/fix/run.rb +88 -0
- data/lib/fix/set.rb +67 -0
- data/lib/fix.rb +85 -20
- data/lib/kernel.rb +49 -0
- metadata +41 -153
- data/.gitignore +0 -11
- data/.rubocop.yml +0 -1
- data/.rubocop_todo.yml +0 -25
- data/.travis.yml +0 -28
- data/.yardopts +0 -1
- data/CODE_OF_CONDUCT.md +0 -13
- data/Gemfile +0 -5
- data/Rakefile +0 -23
- data/VERSION.semver +0 -1
- data/bin/console +0 -8
- data/bin/setup +0 -6
- data/checksum/fix-0.0.1.pre.gem.sha512 +0 -1
- data/checksum/fix-0.1.0.gem.sha512 +0 -1
- data/checksum/fix-0.1.0.pre.gem.sha512 +0 -1
- data/checksum/fix-0.10.0.gem.sha512 +0 -1
- data/checksum/fix-0.11.0.gem.sha512 +0 -1
- data/checksum/fix-0.11.1.gem.sha512 +0 -1
- data/checksum/fix-0.12.0.gem.sha512 +0 -1
- data/checksum/fix-0.12.1.gem.sha512 +0 -1
- data/checksum/fix-0.12.2.gem.sha512 +0 -1
- data/checksum/fix-0.12.3.gem.sha512 +0 -1
- data/checksum/fix-0.13.0.gem.sha512 +0 -1
- data/checksum/fix-0.14.0.gem.sha512 +0 -1
- data/checksum/fix-0.14.1.gem.sha512 +0 -1
- data/checksum/fix-0.15.0.gem.sha512 +0 -1
- data/checksum/fix-0.15.2.gem.sha512 +0 -1
- data/checksum/fix-0.16.0.gem.sha512 +0 -1
- data/checksum/fix-0.17.0.gem.sha512 +0 -1
- data/checksum/fix-0.17.1.gem.sha512 +0 -1
- data/checksum/fix-0.17.2.gem.sha512 +0 -1
- data/checksum/fix-0.18.0.gem.sha512 +0 -1
- data/checksum/fix-0.18.1.gem.sha512 +0 -1
- data/checksum/fix-0.2.0.gem.sha512 +0 -1
- data/checksum/fix-0.3.0.gem.sha512 +0 -1
- data/checksum/fix-0.4.0.gem.sha512 +0 -1
- data/checksum/fix-0.5.0.gem.sha512 +0 -1
- data/checksum/fix-0.6.0.gem.sha512 +0 -1
- data/checksum/fix-0.6.1.gem.sha512 +0 -1
- data/checksum/fix-0.7.0.gem.sha512 +0 -1
- data/checksum/fix-0.8.0.gem.sha512 +0 -1
- data/checksum/fix-0.9.0.gem.sha512 +0 -1
- data/checksum/fix-0.9.1.gem.sha512 +0 -1
- data/fix.gemspec +0 -29
- data/lib/fix/it.rb +0 -41
- data/lib/fix/on.rb +0 -139
- data/lib/fix/report.rb +0 -120
- data/lib/fix/test.rb +0 -89
- data/pkg_checksum +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e44836a2e867dcb513bc3daffa30c6c319e18602be1c4cf0e098d14f7ae4f9b
|
4
|
+
data.tar.gz: 70029c70b7efd078c5d4cafdcdacd1921a2c081e1637f7f2cbc867bfde839781
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f18450b4cc887e269471c529e95816537f620e342733a3441091b01709feaf44d7f1fe46e7ea2b228f5eca154f410de0e03456f238aa642e2a35ff1d55aa5376
|
7
|
+
data.tar.gz: 3854f42a81ddece26f7e9b7d33d347caf37c885cb65ea1e17910032c279c249aefa6ff73d3b7dccfd205d5824812d72564c79d2b76cc7d0afd2e2c51ad08428e
|
data/LICENSE.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
The MIT License
|
1
|
+
# The MIT License
|
2
2
|
|
3
|
-
Copyright (c) 2014-
|
3
|
+
Copyright (c) 2014-2025 Cyril Kato
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -1,152 +1,452 @@
|
|
1
|
-
# Fix
|
1
|
+
# Fix Framework
|
2
2
|
|
3
|
-
[![
|
4
|
-
[![
|
5
|
-
[![
|
6
|
-
[![
|
7
|
-
[![Documentation](http://img.shields.io/:yard-docs-38c800.svg)][rubydoc]
|
8
|
-
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)][gitter]
|
3
|
+
[![Home](https://img.shields.io/badge/Home-fixrb.dev-00af8b)](https://fixrb.dev/)
|
4
|
+
[![Version](https://img.shields.io/github/v/tag/fixrb/fix?label=Version&logo=github)](https://github.com/fixrb/fix/tags)
|
5
|
+
[![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/fixrb/fix/main)
|
6
|
+
[![License](https://img.shields.io/github/license/fixrb/fix?label=License&logo=github)](https://github.com/fixrb/fix/raw/main/LICENSE.md)
|
9
7
|
|
10
|
-
|
8
|
+
## Introduction
|
11
9
|
|
12
|
-
|
13
|
-
|
14
|
-
* Home page: https://fixrb.dev/
|
15
|
-
* Bugs/issues: https://github.com/fixrb/fix/issues
|
16
|
-
* Support: https://stackoverflow.com/questions/tagged/fixrb
|
10
|
+
Fix is a modern Ruby testing framework that emphasizes clear separation between specifications and examples. Unlike traditional testing frameworks, Fix focuses on creating pure specification documents that define expected behaviors without mixing in implementation details.
|
17
11
|
|
18
12
|
## Installation
|
19
13
|
|
20
|
-
|
14
|
+
### Prerequisites
|
15
|
+
|
16
|
+
- Ruby >= 3.1.0
|
17
|
+
|
18
|
+
### Setup
|
19
|
+
|
20
|
+
Add to your Gemfile:
|
21
21
|
|
22
22
|
```ruby
|
23
|
-
gem
|
23
|
+
gem "fix"
|
24
24
|
```
|
25
25
|
|
26
|
-
|
26
|
+
Then execute:
|
27
27
|
|
28
|
-
|
28
|
+
```sh
|
29
|
+
bundle install
|
30
|
+
```
|
29
31
|
|
30
|
-
Or install it yourself
|
32
|
+
Or install it yourself:
|
31
33
|
|
32
|
-
|
34
|
+
```sh
|
35
|
+
gem install fix
|
36
|
+
```
|
33
37
|
|
34
|
-
##
|
38
|
+
## Core Principles
|
35
39
|
|
36
|
-
|
40
|
+
- **Specifications vs Examples**: Fix makes a clear distinction between specifications (what is expected) and examples (how it's demonstrated). This separation leads to cleaner, more maintainable test suites.
|
37
41
|
|
38
|
-
|
42
|
+
- **Logic-Free Specifications**: Your specification documents remain pure and focused on defining behaviors, without getting cluttered by implementation logic.
|
39
43
|
|
40
|
-
|
44
|
+
- **Rich Semantic Language**: Following RFC 2119 conventions, Fix uses precise language with keywords like MUST, SHOULD, and MAY to clearly define different requirement levels in specifications.
|
41
45
|
|
42
|
-
|
46
|
+
- **Fast Individual Testing**: Tests execute quickly and independently, providing rapid feedback on specification compliance.
|
43
47
|
|
44
|
-
|
48
|
+
## Framework Features
|
45
49
|
|
46
|
-
|
50
|
+
### Property Definition with `let`
|
47
51
|
|
48
|
-
|
52
|
+
Define reusable properties across your specifications:
|
49
53
|
|
50
|
-
|
54
|
+
```ruby
|
55
|
+
Fix do
|
56
|
+
let(:name) { "Bob" }
|
57
|
+
let(:age) { 42 }
|
51
58
|
|
52
|
-
|
59
|
+
it MUST eq name
|
60
|
+
end
|
61
|
+
```
|
53
62
|
|
54
|
-
|
63
|
+
### Context Creation with `with`
|
55
64
|
|
56
|
-
|
65
|
+
Test behavior under different conditions:
|
57
66
|
|
58
|
-
|
67
|
+
```ruby
|
68
|
+
Fix do
|
69
|
+
with name: "Alice", role: "admin" do
|
70
|
+
it MUST be_allowed
|
71
|
+
end
|
72
|
+
|
73
|
+
with name: "Bob", role: "guest" do
|
74
|
+
it MUST_NOT be_allowed
|
75
|
+
end
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
### Method Testing with `on`
|
80
|
+
|
81
|
+
Test how objects respond to specific messages:
|
59
82
|
|
60
83
|
```ruby
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
'Klop klop!'
|
84
|
+
Fix do
|
85
|
+
on :upcase do
|
86
|
+
it MUST eq "HELLO"
|
65
87
|
end
|
66
88
|
|
67
|
-
|
68
|
-
|
89
|
+
on :+, 2 do
|
90
|
+
it MUST eq 42
|
69
91
|
end
|
92
|
+
end
|
93
|
+
```
|
70
94
|
|
71
|
-
|
72
|
-
|
95
|
+
### Requirement Levels
|
96
|
+
|
97
|
+
Fix provides three levels of requirements, each with clear semantic meaning:
|
98
|
+
|
99
|
+
- **MUST/MUST_NOT**: Absolute requirements or prohibitions
|
100
|
+
- **SHOULD/SHOULD_NOT**: Recommended practices with valid exceptions
|
101
|
+
- **MAY**: Optional features
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
Fix do
|
105
|
+
it MUST be_valid # Required
|
106
|
+
it SHOULD be_optimized # Recommended
|
107
|
+
it MAY include_metadata # Optional
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
## Quick Start
|
112
|
+
|
113
|
+
Create your first test file:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
# first_test.rb
|
117
|
+
require "fix"
|
118
|
+
|
119
|
+
Fix :HelloWorld do
|
120
|
+
it MUST eq "Hello, World!"
|
121
|
+
end
|
122
|
+
|
123
|
+
Fix[:HelloWorld].test { "Hello, World!" }
|
124
|
+
```
|
125
|
+
|
126
|
+
Run it:
|
127
|
+
|
128
|
+
```sh
|
129
|
+
ruby first_test.rb
|
130
|
+
```
|
131
|
+
|
132
|
+
## Real-World Examples
|
133
|
+
|
134
|
+
Fix is designed to work with real-world applications of any complexity. Here are some examples demonstrating how Fix can be used in different scenarios:
|
135
|
+
|
136
|
+
### Example 1: User Account Management
|
137
|
+
|
138
|
+
Here's a comprehensive example showing how to specify a user account system:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
Fix :UserAccount do
|
142
|
+
# Define reusable properties
|
143
|
+
let(:admin) { User.new(role: "admin") }
|
144
|
+
let(:guest) { User.new(role: "guest") }
|
145
|
+
|
146
|
+
# Test basic instance properties
|
147
|
+
it MUST be_an_instance_of User
|
148
|
+
|
149
|
+
# Test with different contexts
|
150
|
+
with role: "admin" do
|
151
|
+
it MUST be_admin
|
152
|
+
|
153
|
+
on :can_access?, "settings" do
|
154
|
+
it MUST be_true
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
with role: "guest" do
|
159
|
+
it MUST_NOT be_admin
|
160
|
+
|
161
|
+
on :can_access?, "settings" do
|
162
|
+
it MUST be_false
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Test specific methods
|
167
|
+
on :full_name do
|
168
|
+
with first_name: "John", last_name: "Doe" do
|
169
|
+
it MUST eq "John Doe"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
on :update_password, "new_password" do
|
174
|
+
it MUST change(admin, :password_hash)
|
175
|
+
it MUST be_true # Return value check
|
73
176
|
end
|
74
177
|
end
|
75
178
|
```
|
76
179
|
|
77
|
-
|
180
|
+
The implementation might look like this:
|
78
181
|
|
79
182
|
```ruby
|
80
|
-
|
81
|
-
|
82
|
-
require 'fix'
|
183
|
+
class User
|
184
|
+
attr_reader :role, :password_hash
|
83
185
|
|
84
|
-
|
186
|
+
def initialize(role:)
|
187
|
+
@role = role
|
188
|
+
@password_hash = nil
|
189
|
+
end
|
190
|
+
|
191
|
+
def admin?
|
192
|
+
role == "admin"
|
193
|
+
end
|
194
|
+
|
195
|
+
def can_access?(resource)
|
196
|
+
return true if admin?
|
197
|
+
false
|
198
|
+
end
|
199
|
+
|
200
|
+
def full_name
|
201
|
+
"#{@first_name} #{@last_name}"
|
202
|
+
end
|
203
|
+
|
204
|
+
def update_password(new_password)
|
205
|
+
@password_hash = Digest::SHA256.hexdigest(new_password)
|
206
|
+
true
|
207
|
+
end
|
208
|
+
end
|
209
|
+
```
|
210
|
+
|
211
|
+
### Example 2: Duck Specification
|
212
|
+
|
213
|
+
Here's how Fix can be used to specify a Duck class:
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
Fix :Duck do
|
217
|
+
it SHOULD be_an_instance_of :Duck
|
85
218
|
|
86
|
-
Fix.describe @bird do
|
87
219
|
on :swims do
|
88
|
-
it
|
220
|
+
it MUST be_an_instance_of :String
|
221
|
+
it MUST eql "Swoosh..."
|
89
222
|
end
|
90
223
|
|
91
224
|
on :speaks do
|
92
|
-
it
|
225
|
+
it MUST raise_exception NoMethodError
|
93
226
|
end
|
94
227
|
|
95
228
|
on :sings do
|
96
|
-
it
|
229
|
+
it MAY eql "♪... ♫..."
|
230
|
+
end
|
231
|
+
end
|
232
|
+
```
|
233
|
+
|
234
|
+
The implementation:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
class Duck
|
238
|
+
def walks
|
239
|
+
"Klop klop!"
|
240
|
+
end
|
241
|
+
|
242
|
+
def swims
|
243
|
+
"Swoosh..."
|
244
|
+
end
|
245
|
+
|
246
|
+
def quacks
|
247
|
+
puts "Quaaaaaack!"
|
97
248
|
end
|
98
249
|
end
|
99
250
|
```
|
100
251
|
|
101
|
-
|
252
|
+
Running the test:
|
102
253
|
|
103
|
-
|
104
|
-
|
254
|
+
```ruby
|
255
|
+
Fix[:Duck].test { Duck.new }
|
256
|
+
```
|
257
|
+
## Available Matchers
|
258
|
+
|
259
|
+
Fix includes a comprehensive set of matchers through its integration with the [Matchi library](https://github.com/fixrb/matchi):
|
260
|
+
|
261
|
+
<details>
|
262
|
+
<summary><strong>Basic Comparison Matchers</strong></summary>
|
263
|
+
|
264
|
+
- `eq(expected)` - Tests equality using `eql?`
|
265
|
+
```ruby
|
266
|
+
it MUST eq(42) # Passes if value.eql?(42)
|
267
|
+
it MUST eq("hello") # Passes if value.eql?("hello")
|
268
|
+
```
|
269
|
+
- `eql(expected)` - Alias for eq
|
270
|
+
- `be(expected)` - Tests object identity using `equal?`
|
271
|
+
```ruby
|
272
|
+
string = "test"
|
273
|
+
it MUST be(string) # Passes only if it's exactly the same object
|
274
|
+
```
|
275
|
+
- `equal(expected)` - Alias for be
|
276
|
+
</details>
|
277
|
+
|
278
|
+
<details>
|
279
|
+
<summary><strong>Type Checking Matchers</strong></summary>
|
280
|
+
|
281
|
+
- `be_an_instance_of(class)` - Verifies exact class match
|
282
|
+
```ruby
|
283
|
+
it MUST be_an_instance_of(Array) # Passes if value.instance_of?(Array)
|
284
|
+
it MUST be_an_instance_of(User) # Passes if value.instance_of?(User)
|
285
|
+
```
|
286
|
+
- `be_a_kind_of(class)` - Checks class inheritance and module inclusion
|
287
|
+
```ruby
|
288
|
+
it MUST be_a_kind_of(Enumerable) # Passes if value.kind_of?(Enumerable)
|
289
|
+
it MUST be_a_kind_of(Animal) # Passes if value inherits from Animal
|
290
|
+
```
|
291
|
+
</details>
|
292
|
+
|
293
|
+
<details>
|
294
|
+
<summary><strong>Change Testing Matchers</strong></summary>
|
295
|
+
|
296
|
+
- `change(object, method)` - Base matcher for state changes
|
297
|
+
- `.by(n)` - Expects exact change by n
|
298
|
+
```ruby
|
299
|
+
it MUST change(user, :points).by(5) # Exactly +5 points
|
300
|
+
```
|
301
|
+
- `.by_at_least(n)` - Expects minimum change by n
|
302
|
+
```ruby
|
303
|
+
it MUST change(counter, :value).by_at_least(10) # At least +10
|
304
|
+
```
|
305
|
+
- `.by_at_most(n)` - Expects maximum change by n
|
306
|
+
```ruby
|
307
|
+
it MUST change(account, :balance).by_at_most(100) # No more than +100
|
308
|
+
```
|
309
|
+
- `.from(old).to(new)` - Expects change from old to new value
|
310
|
+
```ruby
|
311
|
+
it MUST change(user, :status).from("pending").to("active")
|
312
|
+
```
|
313
|
+
- `.to(new)` - Expects change to new value
|
314
|
+
```ruby
|
315
|
+
it MUST change(post, :title).to("Updated")
|
316
|
+
```
|
317
|
+
</details>
|
318
|
+
|
319
|
+
<details>
|
320
|
+
<summary><strong>Numeric Matchers</strong></summary>
|
321
|
+
|
322
|
+
- `be_within(delta).of(value)` - Tests if a value is within ±delta of expected value
|
323
|
+
```ruby
|
324
|
+
it MUST be_within(0.1).of(3.14) # Passes if value is between 3.04 and 3.24
|
325
|
+
it MUST be_within(5).of(100) # Passes if value is between 95 and 105
|
326
|
+
```
|
327
|
+
</details>
|
328
|
+
|
329
|
+
<details>
|
330
|
+
<summary><strong>Pattern Matchers</strong></summary>
|
331
|
+
|
332
|
+
- `match(regex)` - Tests string against regular expression pattern
|
333
|
+
```ruby
|
334
|
+
it MUST match(/^\d{3}-\d{2}-\d{4}$/) # SSN format
|
335
|
+
it MUST match(/^[A-Z][a-z]+$/) # Capitalized word
|
336
|
+
```
|
337
|
+
- `satisfy { |value| ... }` - Custom matching with block
|
338
|
+
```ruby
|
339
|
+
it MUST satisfy { |num| num.even? && num > 0 }
|
340
|
+
it MUST satisfy { |user| user.valid? && user.active? }
|
341
|
+
```
|
342
|
+
</details>
|
343
|
+
|
344
|
+
<details>
|
345
|
+
<summary><strong>Exception Matchers</strong></summary>
|
346
|
+
|
347
|
+
- `raise_exception(class)` - Tests if code raises specified exception
|
348
|
+
```ruby
|
349
|
+
it MUST raise_exception(ArgumentError)
|
350
|
+
it MUST raise_exception(CustomError, "specific message")
|
351
|
+
```
|
352
|
+
</details>
|
353
|
+
|
354
|
+
<details>
|
355
|
+
<summary><strong>State Matchers</strong></summary>
|
356
|
+
|
357
|
+
- `be_true` - Tests for true
|
358
|
+
```ruby
|
359
|
+
it MUST be_true # Only passes for true, not truthy values
|
360
|
+
```
|
361
|
+
- `be_false` - Tests for false
|
362
|
+
```ruby
|
363
|
+
it MUST be_false # Only passes for false, not falsey values
|
364
|
+
```
|
365
|
+
- `be_nil` - Tests for nil
|
366
|
+
```ruby
|
367
|
+
it MUST be_nil # Passes only for nil
|
368
|
+
```
|
369
|
+
</details>
|
370
|
+
|
371
|
+
<details>
|
372
|
+
<summary><strong>Dynamic Predicate Matchers</strong></summary>
|
373
|
+
|
374
|
+
- `be_*` - Dynamically matches `object.*?` method
|
375
|
+
```ruby
|
376
|
+
it MUST be_empty # Calls empty?
|
377
|
+
it MUST be_valid # Calls valid?
|
378
|
+
it MUST be_frozen # Calls frozen?
|
379
|
+
```
|
380
|
+
- `have_*` - Dynamically matches `object.has_*?` method
|
381
|
+
```ruby
|
382
|
+
it MUST have_key(:id) # Calls has_key?
|
383
|
+
it MUST have_errors # Calls has_errors?
|
384
|
+
it MUST have_permission # Calls has_permission?
|
385
|
+
```
|
386
|
+
</details>
|
387
|
+
|
388
|
+
### Complete Example
|
389
|
+
|
390
|
+
Here's an example using various matchers together:
|
105
391
|
|
106
|
-
|
392
|
+
```ruby
|
393
|
+
Fix :Calculator do
|
394
|
+
it MUST be_an_instance_of Calculator
|
107
395
|
|
108
|
-
|
109
|
-
|
396
|
+
on :add, 2, 3 do
|
397
|
+
it MUST eq 5
|
398
|
+
it MUST be_within(0.001).of(5.0)
|
399
|
+
end
|
400
|
+
|
401
|
+
on :divide, 1, 0 do
|
402
|
+
it MUST raise_exception ZeroDivisionError
|
403
|
+
end
|
110
404
|
|
111
|
-
|
405
|
+
with numbers: [1, 2, 3] do
|
406
|
+
it MUST_NOT be_empty
|
407
|
+
it MUST satisfy { |result| result.all? { |n| n.positive? } }
|
408
|
+
end
|
409
|
+
|
410
|
+
with string_input: "123" do
|
411
|
+
on :parse do
|
412
|
+
it MUST be_a_kind_of Numeric
|
413
|
+
it MUST satisfy { |n| n > 0 }
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
```
|
112
418
|
|
113
|
-
|
114
|
-
every Gem release. These checksums can be found in the `checksum/` directory.
|
115
|
-
Although these checksums do not prevent malicious users from tampering with a
|
116
|
-
built Gem they can be used for basic integrity verification purposes.
|
419
|
+
## Why Choose Fix?
|
117
420
|
|
118
|
-
|
119
|
-
example:
|
421
|
+
Fix brings several unique advantages to Ruby testing that set it apart from traditional testing frameworks:
|
120
422
|
|
121
|
-
|
122
|
-
|
423
|
+
- **Clear Separation of Concerns**: Keep your specifications clean and your examples separate
|
424
|
+
- **Semantic Precision**: Express requirements with different levels of necessity
|
425
|
+
- **Fast Execution**: Get quick feedback on specification compliance
|
426
|
+
- **Pure Specifications**: Write specification documents that focus on behavior, not implementation
|
427
|
+
- **Rich Matcher Library**: Comprehensive set of matchers for different testing needs
|
428
|
+
- **Modern Ruby**: Takes advantage of modern Ruby features and practices
|
123
429
|
|
124
|
-
##
|
430
|
+
## Get Started
|
125
431
|
|
126
|
-
|
432
|
+
Ready to write better specifications? Visit our [GitHub repository](https://github.com/fixrb/fix) to start using Fix in your Ruby projects.
|
127
433
|
|
128
|
-
##
|
434
|
+
## Community & Resources
|
129
435
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
436
|
+
- [Blog](https://fixrb.dev/) - Related articles
|
437
|
+
- [Bluesky](https://bsky.app/profile/fixrb.dev) - Latest updates and discussions
|
438
|
+
- [Documentation](https://www.rubydoc.info/gems/fix) - Comprehensive guides and API reference
|
439
|
+
- [Source Code](https://github.com/fixrb/fix) - Contribute and report issues
|
440
|
+
- [asciinema](https://asciinema.org/~fix) - Watch practical examples in action
|
135
441
|
|
136
|
-
##
|
442
|
+
## Versioning
|
137
443
|
|
138
|
-
|
444
|
+
__Fix__ follows [Semantic Versioning 2.0](https://semver.org/).
|
139
445
|
|
140
|
-
|
141
|
-
[travis]: https://travis-ci.org/fixrb/fix
|
142
|
-
[codeclimate]: https://codeclimate.com/github/fixrb/fix
|
143
|
-
[gemnasium]: https://gemnasium.com/fixrb/fix
|
144
|
-
[inchpages]: http://inch-ci.org/github/fixrb/fix
|
145
|
-
[rubydoc]: http://rubydoc.info/gems/fix/frames
|
146
|
-
[gitter]: https://gitter.im/fixrb/fix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
|
446
|
+
## License
|
147
447
|
|
148
|
-
|
448
|
+
The [gem](https://rubygems.org/gems/fix) is available as open source under the terms of the [MIT License](https://github.com/fixrb/fix/raw/main/LICENSE.md).
|
149
449
|
|
150
|
-
|
450
|
+
## Sponsors
|
151
451
|
|
152
|
-
[
|
452
|
+
This project is sponsored by [Sashité](https://sashite.com/)
|
data/lib/fix/builder.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "doc"
|
4
|
+
require_relative "dsl"
|
5
|
+
require_relative "set"
|
6
|
+
require_relative "error/missing_specification_block"
|
7
|
+
|
8
|
+
module Fix
|
9
|
+
# Handles the creation and setup of Fix specifications.
|
10
|
+
#
|
11
|
+
# The Builder constructs new Fix specification sets following these steps:
|
12
|
+
# 1. Creates a new specification class inheriting from DSL
|
13
|
+
# 2. Defines the specification content using the provided block
|
14
|
+
# 3. Optionally registers the named specification
|
15
|
+
# 4. Returns the built specification set
|
16
|
+
#
|
17
|
+
# @example Create a named specification
|
18
|
+
# Fix::Builder.build(:Calculator) do
|
19
|
+
# on(:add, 2, 3) { it MUST equal 5 }
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# @example Create an anonymous specification
|
23
|
+
# Fix::Builder.build do
|
24
|
+
# it MUST be_positive
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# @see Fix::Set
|
28
|
+
# @see Fix::Dsl
|
29
|
+
# @api private
|
30
|
+
class Builder
|
31
|
+
# Creates a new specification set.
|
32
|
+
#
|
33
|
+
# @param name [String, Symbol, nil] Optional name for the specification
|
34
|
+
# @yieldparam [void] Block containing specification definitions
|
35
|
+
# @yieldreturn [void]
|
36
|
+
# @return [Fix::Set] The constructed specification set
|
37
|
+
# @raise [Fix::Error::InvalidSpecificationName] If name is invalid
|
38
|
+
# @raise [Fix::Error::MissingSpecificationBlock] If no block given
|
39
|
+
def self.build(name = nil, &block)
|
40
|
+
new(name, &block).construct_set
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [String, Symbol, nil] The name of the specification
|
44
|
+
attr_reader :name
|
45
|
+
|
46
|
+
def initialize(name = nil, &block)
|
47
|
+
raise Error::MissingSpecificationBlock unless block
|
48
|
+
|
49
|
+
@name = name
|
50
|
+
@block = block
|
51
|
+
end
|
52
|
+
|
53
|
+
# Constructs and returns a new specification set.
|
54
|
+
#
|
55
|
+
# @return [Fix::Set] The constructed specification set
|
56
|
+
def construct_set
|
57
|
+
klass = create_specification
|
58
|
+
populate_specification(klass)
|
59
|
+
register_if_named(klass)
|
60
|
+
build_set(klass)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# @return [Proc] The block containing specification definitions
|
66
|
+
attr_reader :block
|
67
|
+
|
68
|
+
# Creates a new specification class with context tracking.
|
69
|
+
#
|
70
|
+
# @return [Class] A new class inheriting from Fix::Dsl with CONTEXTS initialized
|
71
|
+
def create_specification
|
72
|
+
::Class.new(Dsl).tap do |klass|
|
73
|
+
klass.const_set(:CONTEXTS, [klass])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Evaluates the specification block in the context of the class.
|
78
|
+
#
|
79
|
+
# @param klass [Class] The class to populate with specifications
|
80
|
+
# @return [void]
|
81
|
+
def populate_specification(klass)
|
82
|
+
klass.instance_eval(&block)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Registers the specification in Fix::Doc if a name was provided.
|
86
|
+
#
|
87
|
+
# @param klass [Class] The specification class to register
|
88
|
+
# @return [void]
|
89
|
+
def register_if_named(klass)
|
90
|
+
Doc.spec_set(name, klass) if name
|
91
|
+
end
|
92
|
+
|
93
|
+
# Creates a new specification set from the populated class.
|
94
|
+
#
|
95
|
+
# @param klass [Class] The populated specification class
|
96
|
+
# @return [Fix::Set] A new specification set
|
97
|
+
def build_set(klass)
|
98
|
+
Set.new(*klass.const_get(:CONTEXTS))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|