Ruby testing libraries
There are a lot of Ruby testing libraries out there and you probably use one of them every day, so it’s about time we dive in to see how they work. This week, the challenge is to create your very own testing library.
Your library should be as tiny as possible – it should be a single file –, but it should have one unique feature you’ve been missing while working with other testing libraries. Of course, it should have a great name and a README, explaining how it works (with code examples) and why it’s awesome.
As always, put the whole thing in a Gist. You can turn it into a gem in a full repository after the contest’s voting period is over. You have one week to get your entry in, so be sure to make something awesome. Good luck!
-
Finished in 1st place with a final score of 3.8/5. (View the Gist)README
TestRocket is a simple, tiny testing library for Ruby 1.9. If => in a hash is a "hash rocket", then +-> and --> for tests should be "test rockets"! Simple syntax: +-> { block that should succeed } --> { block that should fail } These tests would both pass: +-> { 2 == 2 } --> { 10 / 0 } See the demonstration test "suite" for more. Unique features (I hope): * Unary plus/minus to denote expectations. * Ability to fit the entire library into a single tweet/single line.testrocket.rbView full entry# A SINGLE FILE CONTAINING THE TEST LIBRARY # A CLASS TO TEST, AND THE TESTS THEMSELVES # =========================================================== # TESTROCKET TESTING LIBRARY module TestRocket def _test(a, b) send((call rescue()) ? a : b) end def +@; puts _test :_pass, :_fail; end def -@; puts _test :_fail, :_pass; end def _pass; ' OK'; end def _fail; " FAIL @ #{source_location.join(':')}"; end end Proc.send :include, TestRocket # A functionally equivalent single liner. I figured you'd rather be able to read the code and output though. # class Proc;def _t;call rescue();end;def +@;p _t ? '.' : _f; end;def -@; p _t ? _f : '.'; end;def _f; "F "+to_s;end;end # =========================================================== # "DIE" - Simple dice simulator for us to test class Die attr_reader :sides def initialize(sides = 6) raise ArgumentError if sides < 2 @sides = sides end def roll rand(@sides) + 1 end end # =========================================================== # EXAMPLE TEST "SUITE" FOR "DIE" # # USAGE # +-> { block that should succeed } # --> { block that should fail } +-> { Die.new(2) } --> { Die.new(1) } +-> { (1..6) === Die.new(6).roll } +-> { Die.new.sides == 6 } +-> { die = Die.new(6) 1000.times { raise unless (1..6) === die.roll } } +-> { die = Die.new(6) 1000.times { raise if die.roll.zero? } } # These two tests will deliberately fail +-> { raise } --> { true }
-
Finished in 2nd place with a final score of 3.3/5. (View the Gist)README.markdown
Detective
What is it?
Simply put, this is a ruby testing library which asks questions in order to determine compliance.
That is, you ask the user questions with "ask", and test results with "expect". You can also give instructions with "instruct".
The entire testing suite is evaluated using the command-line. e.g.
$ detective ./example_tests.rb We should follow instructions. Follow instructions, press enter [press RETURN to continue] Enter 5: 5How do i write my tests?
In a similar fashion to rspec, you put your tests in "describe" or "it" blocks.
describe "Cool feature" do instruct("We will be testing this cool feature") it "should show the correct word" do instruct("Go to coolwebapp.com") ask("What does it say in the middle of the front page?").expect("Cheezburger") end endHow do i run it?
detective tests1.rb tests2.rb ...Have fun!
example_tests.rb# Our cool tests describe "We should follow instructions" do instruct("Follow instructions, press enter") ask("Enter 5").expect(5) it "even if they dont make sense" do instruct("Enter 22 instead of 1") ask("Enter 1").expect(22) end end describe "More tests" do instruct("We will now ask you several more questions") ask("Enter lolcat").expect('lolcat') it "should drive the user crazy" do ask("Are you crazy yet?").expect('yes') end ask("Do you want me to ask you another question?").expect('yes') instruct("I cant think of another question, goodbye!") end
detective.rbView full entry#!/usr/bin/env ruby # detective # Tests by asking the user questions # # NOTE: requires ruby 1.9.2 and the rainbow gem. # # Usage: # detective [list of ruby test files] # # Syntax: # describe "A test" do # instruct "Go to google" # ask("What is written on the first button?").expect("Google Search") # end # # require 'fiber' require 'rainbow' module Detective # Ask question. e.g. # ask "Where were you last night?" def ask(question) @last_question = question @last_result = Fiber.yield [:ask, question] return self end # Instruct the user. e.g. # instruct "You have the right to remain silent" def instruct(question) Fiber.yield [:instruct, question] return self end # Expect a result from the last question. e.g. # ask("When did you last see M. Ratchett alive?").expect("last evening") def expect(result) if (@last_result||'').strip != result.to_s.downcase Fiber.yield [:fail, @last_question, @last_result, result] end end # Describe a test. e.g. # describe("The suspect") { ask("How many fingers am i holding up?").expect(1) } def describe test, &block Fiber.yield [:describe, test] yield Fiber.yield [:describe_end, test] end alias_method :it, :describe end def run_suite(files) fib = Fiber.new do include Detective files.each { |f| require f } end total_test = 0 total_pass = 0 test_results = [] begin result = nil describe_stack = [] while (command = fib.resume(result)) and !command.nil? and command != :end result = nil case command[0] when :instruct if describe_stack[-1][1] puts "#{command[1]} [press RETURN to continue]" result = STDIN.readline end when :ask if describe_stack[-1][1] puts "#{command[1]}: " result = STDIN.readline.downcase end when :describe describe_stack << [command[1], true] puts "\n#{describe_stack.map{|d|d[0]}.join(' ')}.\n" total_test += 1 when :describe_end test_results << ["#{describe_stack.map{|d|d[0]}.join(' ')}.", describe_stack[-1][1]] total_pass += 1 if describe_stack.pop[1] when :fail describe_stack[-1][1] = false end end rescue FiberError rescue Exception => e puts e.inspect puts e.backtrace end puts "\n" test_results.reverse.each do |test| puts test[0].color(test[1] ? :green : :red) end puts "\n#{total_pass} / #{total_test} Tests passed".color(total_pass == total_test ? :green : :red) exit total_pass == total_test ? 0 : 1 end run_suite(ARGV)
-
Finished in 3rd place with a final score of 3.2/5. (View the Gist)Readme.md
Classy — Class Testing
classy is simple no setup or descriptions just equality(ruby 1.9 only, probably)
test.rb
require './classy' str = Classy "ruby string" str.size == 11 str.size != 11 str.reverse == "what" ary = Classy [3,1,2] ary.size == 3 ary.size != 9 ary.sort == [1,2,3] ary << 4 == [8]
output
$ ruby test.rb .FF...F Expected not 11, but it was 11 from test.rb:5:in `<main>' Expected "what", but it was "gnirts ybur" from test.rb:6:in `<main>' Expected [8], but it was [3, 1, 2, 4] from test.rb:12:in `<main>' 7 tests, 4 passing, 3 failing 0.000182 seconds elapsedexample.rbrequire "./classy" class Adder def initialize(a) @a = a end def add(b) @a + b end end a = Classy Adder.new(4) a.add(2) == 6 a.add(0) == 4 a.add(0) == 2 a.add(0) != 2 str = Classy "ruby string" str.size == 11 str.size != 11 str.reverse == "what" ary = Classy [3,1,2] ary.size == 3 ary.size != 9 ary.sort == [1,2,3] ary << 4 == [8]
classy.rbView full entrymodule Classy def Classy(obj) TestClass.new(obj) end def self.included(klass) Runner.start end class TestClass < BasicObject def initialize(obj) @obj = obj end def method_missing(method, *args, &blk) TestResponse.new @obj.send(method, *args, &blk) end end class TestResponse < Struct.new(:actual) def ==(expected) test expected, true end def !=(expected) test expected, false end private def test(expected, truth) if (actual == expected) == truth Runner.pass else Runner.fail actual, expected, caller, truth end end end module Runner extend self @@pass = 0 @@failures = [] def pass print "." @@pass += 1 end def fail(actual, expected, source, truth) print "F" @@failures << "Expected #{'not ' unless truth}#{expected.inspect}, but it was #{actual.inspect}\n\tfrom #{source[1]}" end def start @@start = Time.now at_exit { finish } end def finish @@failures.each {|f| puts; puts f } puts "\n#{@@pass + @@failures.size} tests, #{@@pass} passing, #{@@failures.size} failing" puts "#{Time.now - @@start} seconds elapsed" end end end include Classy
-
Finished in 4th place with a final score of 3.2/5. (View the Gist)Readme.md
a
a is a testing framework written in 6 lines of code (or 472 characters) which tries to be performant, with eye-catchy reports and easy to use.
Heavily inspired by Konstantin Haase's almost-sinatra, its long-term purpose is to become the fastest testing framework available.
The idea is to stay under 7 lines with less than 80 chars per line. There is room for optimization!
Features
- Setup / Teardown
- Assertions (using the
amethod) - Report tests/assertions/failures
- Keep track of lines where failures happened
Usage
Just clone this gist and run:
ruby example.rbAnd voilà:
example.rbrequire 'a' class MyTestCase < A def setup @user = { :some => :object } end def test_user_has_property a @user[:some] == :object a !@user[:other] end def test_user_not_nil a !@user.nil? end end class MyOtherTestCase < A def setup @foo = [1,2,3] end def test_user_has_property a @foo.length == 3 a @foo[2] > 934 # Should fail at line 27 @foo[1] = 99 a @foo[1] != 2 end def teardown @user = nil end end
a.rbView full entry$n=Time.now;$f=[];$c=[];$a=0;class A;def a c;$a+=1;print c ?"\e[32m.\e[0m":($f\ <<caller[0];"\e[31mF\e[0m");end;def self.inherited(b);$c<<b;end;end;at_exit{$c\ .map{|t|i=t.new;i.setup rescue nil;$t=t.instance_methods(false).map{|m|i.send\ m if !%w(setup teardown).include? m};i.teardown rescue nil};print "\n#{Time.\ now-$n} s.\n#{$t.size} tests, #{$a} assertions, #{$f.size} failures\n\n"+$f.\ map{|f|"\e[31mFailure\e[0m \e[97m@\e[33m #{f}"}.join("\n");exit -1 if $f[0]}
-
Finished in 5th place with a final score of 3.2/5. (View the Gist)sample.rb
require 'supispec' describe 'My Application' do describe 'Hello World' do it 'should be true' do 'hello' != 'world' end it 'should not be true' do 'hello' == 'world' end end end
supispec.rb#!/usr/bin/env ruby if RUBY_PLATFORM.downcase.include?("mswin") || RUBY_PLATFORM.downcase.include?("mingw") require 'win32console' end $indent_level = 0 class SupiSpec def initialize @tests = [] end def print @tests.each do |t| out = case t[1] when :fail then red(t[0]) when :pass then green(t[0]) end puts (' ' * $indent_level) + out end end private def it(description, &block) if block.call @tests << [description, :pass] else @tests << [description, :fail] end end def colorize(text, color_code) "\e[#{color_code}m#{text}\e[0m" end def red(text); colorize(text, 31); end def green(text); colorize(text, 32); end end def describe(description, &block) puts (' ' * $indent_level) + description + ':' $indent_level += 1 test = SupiSpec.new test.instance_eval &block test.print $indent_level -= 1 end
README.textileView full entrySupiSpec
SupiSpec uses a rspec like DSL, but eliminates the need of assertions, or, in
rspecs case, #should. I know it’s ugly with this global variable, but I couln’t
find a nicer solution within the span of my motivation :)Sample
Find a sample in sample.rb
-
Finished in 6th place with a final score of 2.4/5. (View the Gist)README.md
This simple framework allows you to assert things that should happen tomorrow*. For example:
assert_tomorrow { Time.now.wday == 2 } # => :passed # assuming today is Monday (i.e. wday == 1)Another example shows that the time will never be the same again:
today = Time.now assert_tomorrow { Time.now == today } # => :failedAnd sometimes we can't event assert something for sure, because my expectation is too complex!
assert_tomorrow { _why is back } # => :errorOf course, there is a lot room for improvement. You can introduce you own DSL and run tests against your life, for example:
me = Dnnx.instance.clone assert_tomorrow do future_me = Dnnx.instance future_me.is_better_than me end*I didn't invent a time machine, so you have to wait 1 day for test result.
assert_tomorrow.rbView full entrydef assert_tomorrow() sleep(24*60*60); (yield ? :passed : :failed) rescue :error end