Skip to content

Commit

Permalink
import
Browse files Browse the repository at this point in the history
  • Loading branch information
ahoward committed Mar 28, 2009
0 parents commit 2598db9
Show file tree
Hide file tree
Showing 7 changed files with 435 additions and 0 deletions.
159 changes: 159 additions & 0 deletions README
@@ -0,0 +1,159 @@
NAME
testy.rb

DESCRIPTION
a BDD testing framework for ruby that's mad at the world and plans to kick
it's ass in 78 freakin lines of code

SYNOPSIS
Testy.testing 'your code' do
test 'some behaviour' do |result|
ultimate = Ultimate.new

result.check :name, :expect => 42, :actual => ultimate.answer
end
end

PRINCIPLES AND GOALS
. it.should.not.merely.be.a.unit.testing.with.a.clever.dsl

. testing should not require learning a framework. ruby is a great
framework so testy uses it instead, requiring programmers learn exactly 2
new method calls

. testing loc should not dwarf those of the application

. testing framework loc should not dwarf those of the application

. testing frameworks should *never* alter ruby built-ins nor add methods to
Object, Kernel, .et al

. the output of tests should be machine parsable for reporting and ci tools
to easily integrate with

. the output of tests should be beautiful so that humans can read it

. the shape of the test file should not insult the programmer so that tests
can double as sample code

. the testing framework should never alter exception semantics

. hi-jacking at_exit sucks ass

. the exit status of running a test suite should indicate the degree of it's
failure state: the more failures the higher the exit status

. sample code should easily be able to double as a test suite, including
it's output

. testing should improve your code and help your users, not make you want to
kill yourself

. using a format that aligns in terminal is sanity saving when comparing
output

. testing frameworks should provide as few shortcuts for making brittle
tightly coupled tests as possible

SAMPLES

<========< samples/a.rb >========>

~ > cat samples/a.rb

# simple use of testy involves simply writing code, and recording the result
# you expect against the actual result
#
# notice that the output is very pretty and that the exitstatus is 0 when all
# tests pass
#
require 'testy'

Testy.testing 'the kick-ass-ed-ness of testy' do

test 'the ultimate answer to life' do |result|
list = []

list << 42
result.check :a, :expect => 42, :actual => list.first

list << 42.0
result.check :b, 42.0, list.last
end

end

~ > ruby samples/a.rb #=> exitstatus=0

---
the kick-ass-ed-ness of testy:
the ultimate answer to life:
success:
a: 42
b: 42.0


<========< samples/b.rb >========>

~ > cat samples/b.rb

# testy will handle unexpected results and exceptions thrown in your code in
# exactly the same way - by reporting on them in a beautiful fashion and
# continuing to run other tests. notice, however, that an unexpected result
# or raised exception will cause a non-zero exitstatus (equalling the number
# of failed tests) for the suite as a whole. also note that previously
# accumulate expect/actual pairs are still reported on in the error report.
#
require 'testy'

Testy.testing 'the exception handling of testy' do

test 'raising an exception' do |result|
list = []

list << 42
result.check :a, :expect => 42, :actual => list.first

list.method_that_does_not_exist
end

test 'returning unexpected results' do |result|
result.check 'a', 42, 42
result.check :b, :expect => 'forty-two', :actual => 42.0
end

end

~ > ruby samples/b.rb #=> exitstatus=2

---
the exception handling of testy:
raising an exception:
failure:
error:
class: NoMethodError
message: undefined method `method_that_does_not_exist' for [42]:Array
bactrace:
- samples/b.rb:18
- ./lib/testy.rb:63:in `call'
- ./lib/testy.rb:63:in `run'
- /opt/local/lib/ruby/site_ruby/1.8/orderedhash.rb:65:in `each'
- /opt/local/lib/ruby/site_ruby/1.8/orderedhash.rb:65:in `each'
- ./lib/testy.rb:59:in `run'
- ./lib/testy.rb:87:in `testing'
- samples/b.rb:10
expect:
a: 42
actual:
a: 42
returning unexpected results:
failure:
expect:
a: 42
b: forty-two
actual:
a: 42
b: 42.0


URI
59 changes: 59 additions & 0 deletions README.tmpl
@@ -0,0 +1,59 @@
NAME
testy.rb

DESCRIPTION
a BDD testing framework for ruby that's mad at the world and plans to kick
it's ass in 78 freakin lines of code

SYNOPSIS
Testy.testing 'your code' do
test 'some behaviour' do |result|
ultimate = Ultimate.new

result.check :name, :expect => 42, :actual => ultimate.answer
end
end

PRINCIPLES AND GOALS
. it.should.not.merely.be.a.unit.testing.with.a.clever.dsl

. testing should not require learning a framework. ruby is a great
framework so testy uses it instead, requiring programmers learn exactly 2
new method calls

. testing loc should not dwarf those of the application

. testing framework loc should not dwarf those of the application

. testing frameworks should *never* alter ruby built-ins nor add methods to
Object, Kernel, .et al

. the output of tests should be machine parsable for reporting and ci tools
to easily integrate with

. the output of tests should be beautiful so that humans can read it

. the shape of the test file should not insult the programmer so that tests
can double as sample code

. the testing framework should never alter exception semantics

. hi-jacking at_exit sucks ass

. the exit status of running a test suite should indicate the degree of it's
failure state: the more failures the higher the exit status

. sample code should easily be able to double as a test suite, including
it's output

. testing should improve your code and help your users, not make you want to
kill yourself

. using a format that aligns in terminal is sanity saving when comparing
output

. testing frameworks should provide as few shortcuts for making brittle
tightly coupled tests as possible

SAMPLES
@samples
38 changes: 38 additions & 0 deletions gemspec.rb
@@ -0,0 +1,38 @@
#! /usr/bin/env gem build

lib, version = File::basename(File::dirname(File::expand_path(__FILE__))).split %r/-/, 2

require 'rubygems'

Gem::Specification::new do |spec|
$VERBOSE = nil

shiteless = lambda do |list|
list.delete_if do |file|
file =~ %r/\.svn/ or
file =~ %r/\.tmp/
end
end

spec.name = lib
spec.version = version
spec.platform = Gem::Platform::RUBY
spec.summary = lib

spec.files = shiteless[Dir::glob("**/**")]
spec.executables = shiteless[Dir::glob("bin/*")].map{|exe| File::basename exe}

spec.require_path = "lib"

spec.has_rdoc = true # File::exist?("doc")
spec.test_suite_file = "test/#{ lib }.rb" if File::file?("test/#{ lib }.rb")
#spec.add_dependency 'lib', '>= version'
#spec.add_dependency 'fattr'

spec.extensions << "extconf.rb" if File::exists? "extconf.rb"

spec.rubyforge_project = 'codeforpeople'
spec.author = "Ara T. Howard"
spec.email = "ara.t.howard@gmail.com"
spec.homepage = "http://codeforpeople.com/lib/ruby/#{ lib }/"
end
43 changes: 43 additions & 0 deletions gen_readme.rb
@@ -0,0 +1,43 @@
#! /usr/bin/env ruby

require 'pathname'

$VERBOSE=nil

def indent s, n = 2
ws = ' ' * n
s.gsub %r/^/, ws
end

template = IO::read 'README.tmpl'

samples = ''
prompt = '~ > '

Dir['sample*/*'].sort.each do |sample|
samples << "\n" << " <========< #{ sample } >========>" << "\n\n"

cmd = "cat #{ sample }"
samples << indent(prompt + cmd, 2) << "\n\n"
samples << indent(`#{ cmd }`, 4) << "\n"

cmd = "ruby #{ sample }"
#samples << indent(prompt + cmd, 2) << "\n\n"

cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -Ilib #{ sample })'"
out = `#{ cmd } 2>&1`
#samples << indent(`#{ cmd } 2>&1`, 4) << "\n"

exitstatus = " #=> exitstatus=#{ $?.exitstatus }"
cmd = "ruby #{ sample }"
samples << indent(prompt + cmd + exitstatus, 2) << "\n\n"
samples << indent(out, 4) << "\n"

#samples << indent("\n#{ prompt } #{ $?.exitstatus } ### exitstatus\n")
end

#samples.gsub! %r/^/, ' '

readme = template.gsub %r/^\s*@samples\s*$/, samples
open('README', 'w'){|fd| fd.write readme}
print readme
89 changes: 89 additions & 0 deletions lib/testy.rb
@@ -0,0 +1,89 @@
require 'rubygems'
require 'orderedhash'
require 'yaml'

module Testy
class Test
class BadResult < StandardError
end

class OrderedHash < ::OrderedHash
def with_string_keys
oh = self.class.new
each_pair{|key, val| oh[key.to_s] = val}
oh
end
end

class Result
attr_accessor :expect
attr_accessor :actual

def initialize
@expect = OrderedHash.new
@actual = OrderedHash.new
end

def check(name, *args)
options = args.last.is_a?(Hash) ? args.pop : {}
value = args.size==0 ? (options[:expect]||options['expect']) : args.shift
expect[name.to_s] = value
value = args.size==0 ? (options[:actual]||options['actual']) : args.shift
actual[name.to_s] = value
end

def ok?
expect == actual
end
end

attr_accessor :name
attr_accessor :tests
attr_accessor :block

def initialize(*args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}
@name = args.first || options[:name] || options['name']
@tests = OrderedHash.new
@block = block
end

def test(name, &block)
@tests[name.to_s] = block
end

def run port = STDOUT
instance_eval(&@block) if @block
report = OrderedHash.new
status = 0
tests.each do |name, block|
result = Result.new
report[name] =
begin
block.call(result)
raise BadResult, name unless result.ok?
{'success' => result.actual}
rescue Object => e
status += 1
failure = OrderedHash.new
unless e.is_a?(BadResult)
error = OrderedHash.new
error['class'] = e.class.name
error['message'] = e.message.to_s
error['bactrace'] = e.backtrace||[]
failure['error'] = error
end
failure['expect'] = result.expect
failure['actual'] = result.actual
{'failure' => failure}
end
end
port << {name => report}.to_yaml
exit(status)
end
end

def Testy.testing(*args, &block)
Test.new(*args, &block).run
end
end

0 comments on commit 2598db9

Please sign in to comment.