Fix
Project Goals
- Distinguish Specifications from Examples: Clear separation between what is expected (specifications) and how it's demonstrated (examples).
- Logic-Free Specification Documents: Create specifications that are straightforward and free of complex logic, focusing purely on defining expected behaviors.
- Nuanced Semantic Language in Specifications: Utilize a rich, nuanced semantic language, similar to that in RFC 2119, employing keywords like MUST, SHOULD, and MAY to define different levels of requirement in specifications.
- Fast and Individual Test Execution: Enable quick execution of tests on an individual basis, providing immediate feedback on compliance with specifications.
Installation
Add to your Gemfile:
gem "fix", ">= 1.0.0.beta10"
Then execute:
bundle install
Or install it yourself:
gem install fix --pre
Getting Started
Create your first test file:
# first_test.rb
require "fix"
Fix :HelloWorld do
it MUST eq "Hello, World!"
end
Fix[:HelloWorld].test { "Hello, World!" }
Run it:
ruby first_test.rb
This simple example introduces the basic concepts:
Fix
creates a new specificationit MUST
defines a required expectationeq
is a matcher that checks for equalitytest { }
runs the test with the actual value
See the Duck example below for a more complete usage.
DSL Methods
Fix provides a Domain Specific Language (DSL) to write clear and expressive specifications. Here's a complete overview of all available DSL methods:
let
Defines a user-defined property that can be reused across specifications:
Fix do
let(:name) { "Bob" }
let(:age) { 42 }
it MUST eq name # References the name property
on :grow do
it MUST eq age + 1 # Properties can be used in calculations
end
end
with
Creates a context with specific properties, allowing you to test behavior under different conditions:
Fix do
# Single property context
with name: "Alice" do
it MUST eq "Alice"
end
# Multiple properties
with name: "Bob", age: 42 do
it MUST include "Bob"
it SHOULD be_positive
end
# Nested contexts
with user: "admin" do
with permission: "write" do
it MUST be_allowed
end
end
end
on
Defines an example group around a method call, testing how an object responds to specific messages:
Fix do
# Simple method call
on :upcase do
it MUST eq "HELLO"
end
# Method with arguments
on :+, 2 do
it MUST eq 42
end
# Method with keyword arguments
on :resize, width: 100, height: 200 do
it MUST eq [100, 200]
end
# Method chain testing
on :split, " " do
on :first do
it MUST eq "Hello"
end
end
end
it
Defines a concrete specification with a requirement level and matcher:
Fix do
# Required specifications
it MUST eq 42
it MUST_NOT be_nil
# Recommended specifications
it SHOULD be_positive
it SHOULD_NOT be_empty
# Optional specifications
it MAY start_with "Hello"
# Multiple specifications in same context
on :calculate do
it MUST be_an_instance_of Numeric # Type check
it MUST be_positive # Value check
it SHOULD be_within(0.01).of(42) # Range check
end
end
Complete Example
Here's how these DSL methods work together in a real specification:
Fix :UserAccount do
# Define reusable properties
let(:admin) { User.new(role: "admin") }
let(:guest) { User.new(role: "guest") }
# Test basic instance properties
it MUST be_an_instance_of User
# Test with different contexts
with role: "admin" do
it MUST be_admin
on :can_access?, "settings" do
it MUST be_true
end
end
with role: "guest" do
it MUST_NOT be_admin
on :can_access?, "settings" do
it MUST be_false
end
end
# Test specific methods
on :full_name do
with first_name: "John", last_name: "Doe" do
it MUST eq "John Doe"
end
end
on :update_password, "new_password" do
it MUST change(admin, :password_hash)
it MUST be_true # Return value check
end
end
Each DSL method serves a specific purpose in creating clear, maintainable specifications:
let
for defining reusable values and setupwith
for creating specific test contextson
for testing method behaviorit
for defining expectations
Together, they allow you to write specifications that are both readable and comprehensive.
Duck example
Specifications for a Duck
class:
# examples/duck/fix.rb
require "fix"
Fix :Duck do
it SHOULD be_an_instance_of :Duck
on :swims do
it MUST be_an_instance_of :String
it MUST eql "Swoosh..."
end
on :speaks do
it MUST raise_exception NoMethodError
end
on :sings do
it MAY eql "♪... ♫..."
end
end
Implementing the Duck
class:
# examples/duck/app.rb
class Duck
def walks
"Klop klop!"
end
def swims
"Swoosh..."
end
def quacks
puts "Quaaaaaack!"
end
end
Running the test:
# examples/duck/test.rb
require_relative "app"
require_relative "fix"
Fix[:Duck].test { Duck.new }
Execute:
ruby examples/duck/test.rb
Expected output:
examples/duck/fix.rb:22 Success: expected to eq "Hi, my name is Donald!".
examples/duck/fix.rb:27 Success: expected "Swoosh..." to be an instance of String.
examples/duck/fix.rb:46 NoMethodError: undefined method `sings' for <Bob>:Duck.
examples/duck/fix.rb:31 Success: expected to be 9.
examples/duck/fix.rb:13 Success: expected to eq "Hi, my name is Picsou!".
examples/duck/fix.rb:42 Success: undefined method `speaks' for <Bob>:Duck.
examples/duck/fix.rb:8 Success: expected <Bob> to be an instance of Duck.
Quaaaaaack!
examples/duck/fix.rb:50 Success: expected to be nil.
examples/duck/fix.rb:28 Success: expected to eq "Swoosh...".
examples/duck/fix.rb:37 Success: expected to be 10.
examples/duck/fix.rb:18 Success: expected to eq "Hi, my name is Picsou!".
Contact
Versioning
Fix follows Semantic Versioning 2.0.
License
The gem is available as open source under the terms of the MIT License.