Repository is archived
Glimmer DSL for Specification - Pure Ruby Declarative Use Case Specification and Automated Verification
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
 Dependencies

Development

Runtime

~> 0.6.1
~> 2.5.1
 Project Readme

Glimmer DSL for Specification 0.0.5

Pure Ruby Declarative Use Case Specification and Automated Verification

Gem Version Join the chat at https://gitter.im/AndyObtiva/glimmer

Update 2021-11-24: This has been a great experiment, but Dave Aronson just reminded me that although RSpec switched to imperative expect syntax (sacrificing their initial expressiveness for "techincal" reasons), Minitest brought declarative syntax back with Minitest Expectations (e.g. _(a).must_equal b) and kept as an available option, so I will simply leave RSpec to Minitest to address my personal concerns. If there is any reason to revive this project in the future, you will learn about it in commits and releases. Otherwise, thank you for checking this project out and for providing feedback.

Despite Ruby's highly expressive nature, most testing toolkits written in Ruby are either imperative (e.g. using assert or expect), thus losing expressiveness and focusing software engineers on the wrong thing, or mix non-Ruby code with Ruby (e.g. cucumber & gherkin), thus missing out on the simplicity of Ruby.

Glimmer DSL for Specification aims to provide a simple minimalistic and noun-based declarative syntax. No more verbs! It is time to think declaratively not imperatively!

As such, software engineers focus on Requirements Specification at the Use Case level whereby each use case is composed of multiple scenarios. No need to specify scenario steps. The code is the steps!!!

Also, no need for extra DSL constructs for making comparisons in verification statements. Just use plain old Ruby and let the library figure out the rest!

For example:

scenario 'Same-content strings are equal' do
  'string' == 'string'
end

That tells me the whole story without needing either assert or expect. It just contains plain Ruby code for performing the comparison.

Another example:

scenario 'person name consists of first name and last name' do
  person = Person.new(first_name: 'Bob', last_name: 'Winfrey')
  
  fact { person.first_name == 'Bob' }
  fact { person.last_name == 'Winfrey' }
  person.name == 'Bob Winfrey'
end

That states a few extra facts in addition to the last statement in the scenario denoting the final verification. Software engineers will not have to write awkward verification code they hate anymore (e.g. assert or expect) as plain old Ruby comparison code gets the job done in Glimmer DSL for Specification!

Note that this library is very new and experimental, so it might change course significantly. Also, despite the bold ambitious statements, there might be obvious blind spots that your feedback would help shine light upon to improve the library. As such, ideas and suggestions are greatly welcome.

Other Glimmer DSL gems you might be interested in:

Full Example

This library was written specification-first utilizing itself. In fact, here is the initial specification of glimmer-dsl-specification to prove it!

require 'glimmer-dsl-specification'

class Person
  attr_reader :first_name, :last_name
  
  def initialize(first_name: , last_name: )
    @first_name = first_name
    @last_name = last_name
  end
  
  def name
    "#{first_name} #{last_name}"
  end
end

module Glimmer::Specification
  specification('Glimmer DSL for Specification') {
    use_case('Compare Two Objects for Equality') {
      scenario 'Same-content strings are equal' do
        'string' == 'string'
      end
      
      scenario 'Different-content strings are not equal' do
        'string1' != 'string2'
      end
      
      scenario 'Same-number integers are equal' do
        1 == 1
      end
      
      scenario 'Different-number integers are not equal' do
        1 != 2
      end
    }
    
    use_case('Verify Multiple Facts') {
      scenario 'person name consists of first name and last name' do
        person = Person.new(first_name: 'Bob', last_name: 'Winfrey')
        
        fact { person.first_name == 'Bob' }
        fact { person.last_name == 'Winfrey' }
        person.name == 'Bob Winfrey'
      end
    }
  }
end

Output (colored in actual usage):

VERIFIED: Glimmer DSL for Specification - Compare Two Objects for Equality - Same-content strings are equal
VERIFIED: Glimmer DSL for Specification - Compare Two Objects for Equality - Different-content strings are not equal
VERIFIED: Glimmer DSL for Specification - Compare Two Objects for Equality - Same-number integers are equal
VERIFIED: Glimmer DSL for Specification - Compare Two Objects for Equality - Different-number integers are not equal
VERIFIED: Glimmer DSL for Specification - Compare Two Objects for Equality
VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.first_name == 'Bob' }
VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.last_name == 'Winfrey' }
VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name
VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts
VERIFIED: Glimmer DSL for Specification

Suppose we fudge some code in Verify Multiple Facts use case:

require 'glimmer-dsl-specification'

class Person
  attr_reader :first_name, :last_name
  
  def initialize(first_name: , last_name: )
    @first_name = first_name
    @last_name = last_name
  end
  
  def name
    "#{first_name} #{last_name}"
  end
end

module Glimmer::Specification
  specification('Glimmer DSL for Specification') {
    use_case('Compare Two Objects for Equality') {
      scenario 'Same-content strings are equal' do
        'string' == 'string'
      end
      
      scenario 'Different-content strings are not equal' do
        'string1' != 'string2'
      end

      scenario 'Same-number integers are equal' do
        1 == 1
      end

      scenario 'Different-number integers are not equal' do
        1 != 2
      end
    }
    
    use_case('Verify Multiple Facts') {
      scenario 'person name consists of first name and last name' do
        person = Person.new(first_name: 'Bob', last_name: 'Winfrey')

        fact { person.first_name == 'Bob' }
        fact { person.last_name == 'Winfrey' }
        fact { person.last_name == 'aWinfrey' }
        fact { person.last_name != 'Winfrey' }
        fact { person.last_name.empty? }
        fact { person.last_name.include?('fda') }
        fact { person.last_name.nil? }
        fact { [person.last_name] == ['aWinfrey'] }
        fact { [person.last_name] != ['Winfrey'] }
        fact { [person.last_name].empty? }
        fact { [person.last_name].include?('ha') }
        fact { [person.last_name].nil? }
        fact { person == nil }
        fact { person.nil? }
        fact { person != person }
        fact { person.last_name.size == 3 }
        fact { person.last_name.size > 13 }
        fact { person.last_name.size >= 13 }
        fact { person.last_name.size < 1 }
        fact { person.last_name.size <= 1 }
        fact { person.last_name.size != 7 }
        person.name == 'Bob Winfrey'
      end
    }
  }
end

Failure output (colored in actual usage):

VERIFIED: Glimmer DSL for Specification - Compare Two Objects for Equality - Same-content strings are equal
VERIFIED: Glimmer DSL for Specification - Compare Two Objects for Equality - Different-content strings are not equal
VERIFIED: Glimmer DSL for Specification - Compare Two Objects for Equality - Same-number integers are equal
VERIFIED: Glimmer DSL for Specification - Compare Two Objects for Equality - Different-number integers are not equal
VERIFIED: Glimmer DSL for Specification - Compare Two Objects for Equality
VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.first_name == 'Bob' }
VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.last_name == 'Winfrey' }
FAILED: "Winfrey" == "aWinfrey"
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.last_name == 'aWinfrey' }
FAILED: "Winfrey" != "Winfrey"
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.last_name != 'Winfrey' }
FAILED: "Winfrey".empty?
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.last_name.empty? }
FAILED: "Winfrey".include?("fda")
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.last_name.include?('fda') }
FAILED: "Winfrey".nil?
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.last_name.nil? }
FAILED: ["Winfrey"] == ["aWinfrey"]
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { [person.last_name] == ['aWinfrey'] }
FAILED: ["Winfrey"] != ["Winfrey"]
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { [person.last_name] != ['Winfrey'] }
FAILED: ["Winfrey"].empty?
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { [person.last_name].empty? }
FAILED: ["Winfrey"].include?("ha")
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { [person.last_name].include?('ha') }
FAILED: ["Winfrey"].nil?
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { [person.last_name].nil? }
FAILED: #<Person:0x00007f832a93b778 @first_name="Bob", @last_name="Winfrey"> == nil
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person == nil }
FAILED: #<Person:0x00007f832a93b778 @first_name="Bob", @last_name="Winfrey">.nil?
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.nil? }
FAILED: #<Person:0x00007f832a93b778 @first_name="Bob", @last_name="Winfrey"> != #<Person:0x00007f832a93b778 @first_name="Bob", @last_name="Winfrey">
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person != person }
FAILED: 7 == 3
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.last_name.size == 3 }
FAILED: 7 > 13
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.last_name.size > 13 }
FAILED: 7 >= 13
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.last_name.size >= 13 }
FAILED: 7 < 1
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.last_name.size < 1 }
FAILED: 7 <= 1
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.last_name.size <= 1 }
FAILED: 7 != 7
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name - fact { person.last_name.size != 7 }
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts - person name consists of first name and last name
NOT VERIFIED: Glimmer DSL for Specification - Verify Multiple Facts
NOT VERIFIED: Glimmer DSL for Specification

Note: Currently, you only get FAILED printout under fact {} blocks, but not as the last statement of scenario. This should change in the forseeable future. Favor declaring criteria in fact {} blocks for now.

Usage

1 - Include in Gemfile (:development or :test group):

gem 'glimmer-dsl-specification', '~> 0.0.5'

And, run:

bundle

2 - Create specification files with _specification.rb extension under specification directory utilizing specification, use_case, scenario, and fact keywords explained in DSL section by adding require statement on top and inserting specification/verification code in Glimmer::Specification module:

require 'glimmer-dsl-specification'

module Glimmer::Specification
  specification('title of specification') {
    use_case('title of use case') {
      scenario('second scenario') {
        fact { something2 == something_else2 } # optional
        fact { something3 != something_else3 } # optional
        fact { something1.include?(something_else1) } # optional
        something == something_else # final verification can be a fact if preferred
      }
    }
  }
end

3 - Run a specification directly with ruby:

ruby specification/lib/glimmer-dsl-specification_specification.rb

4 - Alternatively run rake verify task assuming you have rake gem installed/configured in Gemfile:

rake verify

It is also recommended that you add the following lines to your Rakefile:

require 'glimmer-dsl-specification' # unless loaded by Bundler
require 'glimmer/specification/rake_tasks'

task :default => :verify

That way, you can simply run:

rake

DSL

The Domain Specific Language consists of the following keywords simply nested under Glimmer::Specification module (to avoid namespace pollution).

This library highly emphasizes declarative specification, so it avoids unit-test jargon including "class", "method", "attribute", or "assert" as that is not the ultimate aim of the library, yet specifying application requirements.

Specifications do not care about what specific "classes" or "methods" are executed. They only care about meeting the verification criteria.

specification

(nested directly under Glimmer::Specification module)

specification(title) is the top-level keyword denoting a requirement specification.

use_case

(nested under specification)

use_case(title) represents one or more use cases that the requirement specification consists of

scenario

(nested under use_case or directly under specification for very simple cases)

scenario(title) represents one scenario in a use case that performs a verification by setting up preconditions, performing an action, and returning the final result as a postconditition boolean or alternatively relying on nested fact occurances.

fact

(nested under scenario)

fact {} states a fact embodied by a boolean result for the passed block of code.

  • Upon failure of a fact with Object nil?, ==/!= verification methods, the library will automatically print the values of the involved objects.
  • Upon failure of a fact with String #empty?/#include? verification methods, the library will automatically print the values of the involved objects.
  • Upon failure of a fact with Array #empty?/#include? verification methods, the library will automatically print the values of the involved objects.
  • Upon failure of a fact with Integer >/>=/</<= verification methods, the library will automatically print the values of the involved objects.

Process

Glimmer Process

Resources

Help

Issues

If you encounter issues that are not reported, discover missing features that are not mentioned in TODO.md, or think up better ways to write declarative automated tests, you may submit an issue or pull request on GitHub. In the meantime, you may try an older version of the Ruby gem in case it works better for you until your issues are resolved.

Chat

If you need live help, try to Join the chat at https://gitter.im/AndyObtiva/glimmer

TODO

TODO.md

Change Log

CHANGELOG.md

Contributing

  • Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
  • Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
  • Fork the project.
  • Start a feature/bugfix branch.
  • Commit and push until you are happy with your contribution.
  • Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
  • Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.

Copyright

MIT

Copyright (c) 2021 Andy Maleh. See LICENSE.txt for further details.

--

Built for Glimmer (DSL Framework).