├── VERSION ├── .gitignore ├── test ├── teststrap.rb ├── assertion_macros │ ├── assertion_macro_nil_test.rb │ ├── assertion_macro_kind_of_test.rb │ ├── assertion_macro_equals_test.rb │ ├── assertion_macro_exists_test.rb │ ├── assertion_macro_respond_to_test.rb │ ├── assertion_macro_matching_test.rb │ ├── assertion_macro_assigns_test.rb │ └── assertion_macro_raises_test.rb ├── benchmark │ ├── riot_vs_minitest.rb │ └── simple_context_and_assertions.rb ├── assertion_test.rb └── context_test.rb ├── lib ├── riot │ ├── situation.rb │ ├── errors.rb │ ├── assertion.rb │ ├── context.rb │ ├── report.rb │ └── assertion_macros.rb └── riot.rb ├── MIT-LICENSE ├── Rakefile ├── riot.gemspec └── README.markdown /VERSION: -------------------------------------------------------------------------------- 1 | 0.9.12 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | pkg 3 | -------------------------------------------------------------------------------- /test/teststrap.rb: -------------------------------------------------------------------------------- 1 | require 'riot' 2 | -------------------------------------------------------------------------------- /lib/riot/situation.rb: -------------------------------------------------------------------------------- 1 | module Riot 2 | class Situation 3 | attr_accessor :topic 4 | 5 | def fail(message) 6 | raise Failure.new(message) 7 | end 8 | end # Situation 9 | end # Riot -------------------------------------------------------------------------------- /test/assertion_macros/assertion_macro_nil_test.rb: -------------------------------------------------------------------------------- 1 | require 'teststrap' 2 | 3 | context "nil assertion:" do 4 | setup { Riot::Situation.new } 5 | 6 | asserts("result is nil") do 7 | Riot::Assertion.new("foo", topic) { nil }.nil 8 | end 9 | 10 | should "raise a Failure if not nil" do 11 | Riot::Assertion.new("foo", topic) { "a" }.nil 12 | end.kind_of(Riot::Failure) 13 | end # nil assertion 14 | -------------------------------------------------------------------------------- /lib/riot/errors.rb: -------------------------------------------------------------------------------- 1 | module Riot 2 | class Failure < Exception 3 | def print_stacktrace?; false; end 4 | end 5 | 6 | class Error < Failure 7 | attr_reader :original_exception 8 | def initialize(message, raised) 9 | super(message) 10 | set_backtrace(raised.backtrace) 11 | @original_exception = raised 12 | end 13 | def print_stacktrace?; true; end 14 | end 15 | end # Riot 16 | -------------------------------------------------------------------------------- /test/assertion_macros/assertion_macro_kind_of_test.rb: -------------------------------------------------------------------------------- 1 | require 'teststrap' 2 | 3 | context "kind_of assertion:" do 4 | setup { Riot::Situation.new } 5 | 6 | asserts "specific result is a kind of String" do 7 | Riot::Assertion.new("foo", topic) { "a" }.kind_of(String) 8 | end 9 | 10 | should "raise a Failure if not a kind of String" do 11 | Riot::Assertion.new("foo", topic) { 0 }.kind_of(String) 12 | end.kind_of(Riot::Failure) 13 | end # kind_of assertion -------------------------------------------------------------------------------- /test/assertion_macros/assertion_macro_equals_test.rb: -------------------------------------------------------------------------------- 1 | require 'teststrap' 2 | 3 | context "equals assertion:" do 4 | setup { Riot::Situation.new } 5 | 6 | asserts "result equals expectation" do 7 | Riot::Assertion.new("i will pass", topic) { "foo bar" }.equals("foo bar") 8 | end 9 | 10 | should "raise a Failure if results don't equal each other" do 11 | Riot::Assertion.new("failure", topic) { "bar" }.equals("foo") 12 | end.kind_of(Riot::Failure) 13 | end # equals assertion 14 | -------------------------------------------------------------------------------- /test/assertion_macros/assertion_macro_exists_test.rb: -------------------------------------------------------------------------------- 1 | require 'teststrap' 2 | 3 | context "nil assertion:" do 4 | setup { Riot::Situation.new } 5 | 6 | asserts("result has a value") do 7 | Riot::Assertion.new("foo", topic) { "foo" }.exists 8 | end 9 | 10 | asserts("empty string is considered a value") do 11 | Riot::Assertion.new("foo", topic) { "" }.exists 12 | end 13 | 14 | should "raise a Failure if value is nil" do 15 | Riot::Assertion.new("foo", topic) { nil }.exists 16 | end.kind_of(Riot::Failure) 17 | end # nil assertion 18 | -------------------------------------------------------------------------------- /test/assertion_macros/assertion_macro_respond_to_test.rb: -------------------------------------------------------------------------------- 1 | require 'teststrap' 2 | 3 | context "respond to" do 4 | setup do 5 | Riot::Situation.new 6 | end 7 | 8 | should "pass when object responds to expected method" do 9 | Riot::Assertion.new("foo", topic) { "foo" }.respond_to(:each_byte) 10 | end 11 | 12 | should "fail when object does not respond to expected method" do 13 | Riot::Assertion.new("foo", topic) { "foo" }.respond_to(:goofballs).message 14 | end.equals("foo: expected method :goofballs is not defined") 15 | 16 | end # respond to 17 | -------------------------------------------------------------------------------- /test/assertion_macros/assertion_macro_matching_test.rb: -------------------------------------------------------------------------------- 1 | require 'teststrap' 2 | 3 | context "matching assertion:" do 4 | setup { Riot::Situation.new } 5 | 6 | asserts "result matches expression" do 7 | Riot::Assertion.new("foo", topic) { "a" }.matches(%r[.]) 8 | end.equals(0) 9 | 10 | should "raise a Failure if result does not match" do 11 | Riot::Assertion.new("foo", topic) { "" }.matches(%r[.]) 12 | end.kind_of(Riot::Failure) 13 | 14 | should "return the result of a matching operation" do 15 | Riot::Assertion.new("foo", topic) { "a" }.matches("a") 16 | end.equals(0) 17 | end # maching assertion 18 | -------------------------------------------------------------------------------- /lib/riot.rb: -------------------------------------------------------------------------------- 1 | require 'riot/errors' 2 | require 'riot/report' 3 | require 'riot/situation' 4 | require 'riot/context' 5 | require 'riot/assertion' 6 | require 'riot/assertion_macros' 7 | 8 | module Riot 9 | 10 | # Configuration 11 | 12 | def self.reporter; @reporter ||= (Riot.silently? ? NilReport.new : TextReport.new); end 13 | def self.reporter=(report); @reporter = report; end 14 | def self.silently!; @silently = true; end 15 | def self.silently?; @silently || false; end 16 | 17 | at_exit do 18 | Riot.reporter.results 19 | exit false unless reporter.passed? 20 | end unless Riot.silently? 21 | end # Riot 22 | 23 | module Kernel 24 | def context(description, reporter = nil, parent = nil, &block) 25 | reporter ||= Riot.reporter 26 | reporter.time { Riot::Context.new(description, reporter, parent, &block) } 27 | end 28 | end # Kernel 29 | -------------------------------------------------------------------------------- /test/benchmark/riot_vs_minitest.rb: -------------------------------------------------------------------------------- 1 | $:.concat ['./lib'] 2 | require 'benchmark' 3 | 4 | # 5 | # Model 6 | 7 | class Room 8 | attr_reader :name 9 | def initialize(name) 10 | @name = name 11 | end 12 | end 13 | 14 | # 15 | # Riot 16 | 17 | require 'riot' 18 | Riot.silently! 19 | 20 | # 21 | # MiniTest::Unit 22 | 23 | require 'rubygems' 24 | require 'minitest/unit' 25 | 26 | class RoomTest < MiniTest::Unit::TestCase 27 | def setup 28 | @room = Room.new("bed") 29 | end 30 | 31 | def test_room_should_be_named_bed 32 | assert_equal "bed", @room.name 33 | end 34 | end 35 | 36 | # 37 | # Benchmarking 38 | 39 | n = 100 * 100 40 | 41 | Benchmark.bmbm do |x| 42 | x.report("Riot") do 43 | n.times do 44 | context "a room" do 45 | setup { Room.new("bed") } 46 | 47 | asserts("name") { topic.name }.equals("bed") 48 | end # a room 49 | end 50 | end 51 | 52 | x.report("MiniTest") do 53 | n.times { RoomTest.new("Blah").run(MiniTest::Unit.new) } 54 | end 55 | end 56 | 57 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 Justin Knowlden, Thumble Monks 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/assertion_test.rb: -------------------------------------------------------------------------------- 1 | require 'teststrap' 2 | 3 | context "basic assertion" do 4 | setup { Riot::Situation.new } 5 | 6 | should "have a description" do 7 | Riot::Assertion.new("i will pass", topic).to_s 8 | end.equals("i will pass") 9 | 10 | asserts "pass? is true when assertion passed" do 11 | Riot::Assertion.new("i will pass", topic) { true }.passed? 12 | end 13 | 14 | asserts "failure? is true when assertion does not pass" do 15 | Riot::Assertion.new("i will pass", topic) { false }.failed? 16 | end 17 | 18 | asserts "error? is true when an unexpected Exception is raised" do 19 | Riot::Assertion.new("error", topic) { raise Exception, "blah" }.errored? 20 | end 21 | 22 | context "that fails while executing a test" do 23 | setup do 24 | fake_situation = Riot::Situation.new 25 | Riot::Assertion.new("error", fake_situation) { fail("I'm a bum") } 26 | end 27 | 28 | should("be considered a failing assertion") { topic.failed? } 29 | should("use failed message in description") { topic.result.message }.matches(/I'm a bum/) 30 | end # that fails while executing test 31 | end # basic assertion 32 | -------------------------------------------------------------------------------- /lib/riot/assertion.rb: -------------------------------------------------------------------------------- 1 | module Riot 2 | class Assertion 3 | attr_reader :raised, :to_s, :description, :situation 4 | def initialize(description, situation, &assertion_block) 5 | @description = @to_s = description 6 | @situation = situation 7 | run(situation, &assertion_block) 8 | end 9 | 10 | def actual 11 | unfail_if_default_failure_recorded 12 | @actual 13 | end 14 | 15 | def fail(message) 16 | @failure = Failure.new("#{description}: #{message}") unless errored? 17 | end 18 | 19 | def passed?; !(failed? || errored?); end 20 | def failed?; !@failure.nil?; end 21 | def errored?; !@raised.nil?; end 22 | def result; @failure || @raised; end 23 | private 24 | def run(situation, &assertion_block) 25 | @actual = situation.instance_eval(&assertion_block) 26 | @default_failure = fail("expected true, not #{@actual.inspect}") unless @actual == true 27 | rescue Failure => e 28 | @failure = e 29 | rescue Exception => e 30 | @raised = Error.new("#{description}: errored with #{e}", e) 31 | end 32 | 33 | def unfail_if_default_failure_recorded 34 | @default_failure = @failure = nil if @default_failure 35 | end 36 | end # Assertion 37 | end # Riot -------------------------------------------------------------------------------- /test/benchmark/simple_context_and_assertions.rb: -------------------------------------------------------------------------------- 1 | $:.concat ['./lib'] 2 | require 'benchmark' 3 | 4 | # 5 | # Model 6 | 7 | class Room 8 | attr_reader :name 9 | def initialize(name) 10 | @name = name 11 | end 12 | end 13 | 14 | # 15 | # Test::Unit 16 | 17 | require 'test/unit' 18 | Test::Unit.run = true 19 | 20 | require 'test/unit/ui/console/testrunner' 21 | 22 | class RoomTest < Test::Unit::TestCase 23 | def setup 24 | @room = Room.new("bed") 25 | end 26 | 27 | def test_room_should_be_named_bed 28 | assert_equal "bed", @room.name 29 | end 30 | end 31 | 32 | # 33 | # Shoulda 34 | 35 | require 'rubygems' 36 | require 'shoulda' 37 | 38 | class ShouldaRoomTest < Test::Unit::TestCase 39 | def setup 40 | @room = Room.new("bed") 41 | end 42 | 43 | should("be named 'bed'") { assert_equal "bed", @room.name } 44 | end 45 | 46 | # 47 | # Riot 48 | 49 | require 'riot' 50 | Riot.silently! 51 | 52 | # 53 | # Benchmarking 54 | 55 | n = 100 * 100 56 | 57 | Benchmark.bmbm do |x| 58 | x.report("Riot") do 59 | n.times do 60 | context "a room" do 61 | setup { @room = Room.new("bed") } 62 | 63 | asserts("name") { @room.name }.equals("bed") 64 | end # a room 65 | end 66 | end 67 | 68 | x.report("Test::Unit") do 69 | n.times { Test::Unit::UI::Console::TestRunner.new(RoomTest, Test::Unit::UI::SILENT) } 70 | end 71 | 72 | x.report("Shoulda") do 73 | n.times { Test::Unit::UI::Console::TestRunner.new(ShouldaRoomTest, Test::Unit::UI::SILENT) } 74 | end 75 | end 76 | 77 | -------------------------------------------------------------------------------- /lib/riot/context.rb: -------------------------------------------------------------------------------- 1 | module Riot 2 | class Context 3 | # The protein 4 | attr_reader :description, :assertions, :situation 5 | def initialize(description, reporter, parent=nil, &context_block) 6 | @description, @reporter, @parent = description, reporter, parent 7 | @setups, @assertions = [], [] 8 | @situation = Situation.new 9 | bootstrap(@situation) 10 | instance_eval(&context_block) if block_given? # running the context 11 | report 12 | end 13 | 14 | # Walk it out. Call setup from parent first 15 | def bootstrap(a_situation) 16 | @parent.bootstrap(a_situation).each do |setup_block| 17 | run_setup(a_situation, &setup_block) 18 | end if @parent 19 | @setups 20 | end 21 | 22 | def report 23 | assertions.each { |assertion| @reporter.process_assertion(assertion) } 24 | end 25 | 26 | def to_s; @to_s ||= [@parent.to_s, @description].join(' ').strip; end 27 | 28 | # DSLee stuff 29 | 30 | def setup(&block) 31 | @setups << block 32 | run_setup(situation, &block) 33 | end 34 | 35 | def context(description, &block) Context.new(description, @reporter, self, &block); end 36 | def asserts(what, &block) add_assertion("asserts #{what}", &block); end 37 | def should(what, &block) add_assertion("should #{what}", &block); end 38 | def topic; asserts("topic") { topic }; end 39 | private 40 | def add_assertion(what, &block) 41 | (assertions << Assertion.new("#{to_s} #{what}", @situation, &block)).last 42 | end 43 | 44 | def run_setup(a_situation, &setup_block) 45 | a_situation.topic = a_situation.instance_eval(&setup_block) if setup_block 46 | end 47 | end # Context 48 | end # Riot 49 | -------------------------------------------------------------------------------- /test/assertion_macros/assertion_macro_assigns_test.rb: -------------------------------------------------------------------------------- 1 | require 'teststrap' 2 | 3 | context "assigns assertion" do 4 | setup do 5 | @fake_situation = Riot::Situation.new 6 | object_with_instance_variables = Riot::Situation.new 7 | object_with_instance_variables.instance_eval { @foo = "bar"; @bar = nil} 8 | object_with_instance_variables 9 | end 10 | 11 | asserts("an instance variable was assigned") do 12 | test_object = topic 13 | Riot::Assertion.new("duh", @fake_situation) { test_object }.assigns(:foo) 14 | end 15 | 16 | asserts("an instance variable was never assigned") do 17 | test_object = topic 18 | Riot::Assertion.new("foo", @fake_situation) { test_object }.assigns(:baz) 19 | end.kind_of(Riot::Failure) 20 | 21 | asserts "an instance variable was defined with nil value" do 22 | test_object = topic 23 | Riot::Assertion.new("foo", @fake_situation) { test_object }.assigns(:bar).message 24 | end.matches(/expected @bar to be assigned a value/) 25 | 26 | asserts("an instance variable was assigned a specific value") do 27 | test_object = topic 28 | Riot::Assertion.new("duh", @fake_situation) { test_object }.assigns(:foo, "bar") 29 | end 30 | 31 | asserts("failure when instance never assigned even when a value is expected") do 32 | test_object = topic 33 | Riot::Assertion.new("duh", @fake_situation) { test_object }.assigns(:bar, "bar").message 34 | end.matches(/expected @bar to be assigned a value/) 35 | 36 | asserts("failure when expected value is not assigned to variable with a value") do 37 | test_object = topic 38 | Riot::Assertion.new("duh", @fake_situation) { test_object }.assigns(:foo, "baz").message 39 | end.matches(/expected @foo to be equal to 'baz', not 'bar'/) 40 | end # assigns assertion 41 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | 4 | task :default => [:test] 5 | 6 | require 'rake/testtask' 7 | Rake::TestTask.new(:test) do |test| 8 | test.libs << 'lib' << 'test' 9 | test.pattern = 'test/**/*_test.rb' 10 | test.verbose = false 11 | end 12 | 13 | desc "Run Flog against library (except tests)" 14 | task :flog do 15 | puts %x[find ./lib -name *.rb | xargs flog] 16 | end 17 | 18 | desc "Run Flay against library (except tests)" 19 | task :flay do 20 | puts %x[find ./lib -name *.rb | xargs flay] 21 | end 22 | 23 | desc "Run Roodi against library (except tests)" 24 | task :roodi do 25 | puts %x[find ./lib -name *.rb | xargs roodi] 26 | end 27 | 28 | desc "Stats on lines of code and test" 29 | task :stats do 30 | loc = %x[find ./lib -name *.rb | xargs cat | wc -l].strip.to_i 31 | lotc = %x[find ./test -name *.rb | xargs cat | wc -l].strip.to_i 32 | total, ratio = (loc + lotc), (lotc / loc.to_f) 33 | 34 | fmt = " Code: %d\n Test: %d\n -----\n Total: %d Ratio (test/code): %f" 35 | puts fmt % [loc, lotc, loc + lotc, ratio] 36 | end 37 | 38 | # 39 | # Some monks like diamonds. I like gems. 40 | 41 | begin 42 | require 'jeweler' 43 | Jeweler::Tasks.new do |gem| 44 | gem.name = "riot" 45 | gem.summary = "An extremely fast, expressive, and context-driven unit-testing framework. Protest the slow test." 46 | gem.description = "An extremely fast, expressive, and context-driven unit-testing framework. A replacement for all other testing frameworks. Protest the slow test." 47 | gem.email = "gus@gusg.us" 48 | gem.homepage = "http://github.com/thumblemonks/riot" 49 | gem.authors = ["Justin 'Gus' Knowlden"] 50 | end 51 | Jeweler::GemcutterTasks.new 52 | rescue LoadError 53 | puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" 54 | end 55 | -------------------------------------------------------------------------------- /test/assertion_macros/assertion_macro_raises_test.rb: -------------------------------------------------------------------------------- 1 | require 'teststrap' 2 | 3 | class MyException < Exception; end 4 | 5 | context "raises assertion:" do 6 | setup { Riot::Situation.new } 7 | 8 | should("raise an Exception") { raise Exception }.raises(Exception) 9 | 10 | should "fail if nothing was raised" do 11 | assertion = Riot::Assertion.new("foo", topic) { "barf" } 12 | assertion.raises(Exception) 13 | assertion.result.message 14 | end.matches(/should have raised Exception, but raised nothing/) 15 | 16 | should "fail if Exception classes do not match" do 17 | Riot::Assertion.new("foo", topic) { raise MyException }.raises(Exception) 18 | end.kind_of(Riot::Failure) 19 | 20 | should "pass if provided message equals expectation" do 21 | Riot::Assertion.new("foo", topic) { raise Exception, "I'm a nerd" }.raises(Exception, "I'm a nerd") 22 | end 23 | 24 | should "fail if provided message does not equal expectation" do 25 | Riot::Assertion.new("foo", topic) { raise(Exception, "I'm a nerd") }.raises(Exception, "But I'm not") 26 | end.kind_of(Riot::Failure) 27 | 28 | should "pass if provided message matches expectation" do 29 | Riot::Assertion.new("foo", topic) { raise(Exception, "I'm a nerd") }.raises(Exception, /nerd/) 30 | end 31 | 32 | should "fail if provided message does not match expectation" do 33 | Riot::Assertion.new("foo", topic) { raise(Exception, "I'm a nerd") }.raises(Exception, /foo/) 34 | end.kind_of(Riot::Failure) 35 | 36 | should "pass if provided message as array equals expectation" do 37 | Riot::Assertion.new("foo", topic) { raise(Exception, ["foo", "bar"]) }.raises(Exception, "foobar") 38 | end 39 | 40 | should "pass if provided message as array matches expectation" do 41 | Riot::Assertion.new("foo", topic) { raise(Exception, ["foo", "bar"]) }.raises(Exception, /oba/) 42 | end 43 | end # raises assertion -------------------------------------------------------------------------------- /lib/riot/report.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | 3 | module Riot 4 | class Report 5 | attr_reader :bad_results, :passes, :failures, :errors, :time_taken 6 | def initialize 7 | @bad_results = [] 8 | @passes, @failures, @errors, @time_taken = 0, 0, 0, 0.0 9 | end 10 | 11 | def passed?; failures + errors == 0; end 12 | def assertions; passes + failures + errors; end 13 | 14 | def time(&block) 15 | @start = Time.now 16 | result = yield 17 | @time_taken += (Time.now - @start).to_f 18 | result 19 | end 20 | 21 | def process_assertion(assertion) 22 | if assertion.passed? 23 | passed 24 | else 25 | send((assertion.errored? ? :errored : :failed), assertion.result) 26 | end 27 | end 28 | 29 | def passed; @passes += 1; end 30 | 31 | def failed(failure) 32 | @failures += 1 33 | @bad_results << failure 34 | end 35 | 36 | def errored(error) 37 | @errors += 1 38 | @bad_results << error 39 | end 40 | end # Report 41 | 42 | class NilReport < Report 43 | def results; end 44 | def time(&block); yield; end 45 | end # NilReport 46 | 47 | class TextReport < Report 48 | def initialize(writer=nil) 49 | super() 50 | @writer ||= STDOUT 51 | end 52 | 53 | def passed 54 | super && @writer.print('.') 55 | end 56 | 57 | def failed(failure) 58 | super && @writer.print('F') 59 | end 60 | 61 | def errored(error) 62 | super && @writer.print('E') 63 | end 64 | 65 | def results 66 | @writer.puts "\n\n" 67 | print_bad_results 68 | format = "%d assertions, %d failures, %d errors in %s seconds" 69 | @writer.puts format % [assertions, failures, errors, ("%0.6f" % time_taken)] 70 | end 71 | private 72 | def print_bad_results 73 | bad_results.each_with_index do |result, idx| 74 | @writer.puts format_result(idx + 1, result) 75 | @writer.puts " " + result.backtrace.join("\n ") if result.print_stacktrace? 76 | @writer.puts "\n" 77 | end 78 | end 79 | 80 | def format_result(idx, result) "#%d - %s" % [idx, result.to_s]; end 81 | end # TextReport 82 | end # Riot 83 | -------------------------------------------------------------------------------- /riot.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{riot} 8 | s.version = "0.9.12" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Justin 'Gus' Knowlden"] 12 | s.date = %q{2009-10-22} 13 | s.description = %q{An extremely fast, expressive, and context-driven unit-testing framework. A replacement for all other testing frameworks. Protest the slow test.} 14 | s.email = %q{gus@gusg.us} 15 | s.extra_rdoc_files = [ 16 | "README.markdown" 17 | ] 18 | s.files = [ 19 | ".gitignore", 20 | "MIT-LICENSE", 21 | "README.markdown", 22 | "Rakefile", 23 | "VERSION", 24 | "lib/riot.rb", 25 | "lib/riot/assertion.rb", 26 | "lib/riot/assertion_macros.rb", 27 | "lib/riot/context.rb", 28 | "lib/riot/errors.rb", 29 | "lib/riot/report.rb", 30 | "lib/riot/situation.rb", 31 | "riot.gemspec", 32 | "test/assertion_macros/assertion_macro_assigns_test.rb", 33 | "test/assertion_macros/assertion_macro_equals_test.rb", 34 | "test/assertion_macros/assertion_macro_exists_test.rb", 35 | "test/assertion_macros/assertion_macro_kind_of_test.rb", 36 | "test/assertion_macros/assertion_macro_matching_test.rb", 37 | "test/assertion_macros/assertion_macro_nil_test.rb", 38 | "test/assertion_macros/assertion_macro_raises_test.rb", 39 | "test/assertion_macros/assertion_macro_respond_to_test.rb", 40 | "test/assertion_test.rb", 41 | "test/benchmark/riot_vs_minitest.rb", 42 | "test/benchmark/simple_context_and_assertions.rb", 43 | "test/context_test.rb", 44 | "test/teststrap.rb" 45 | ] 46 | s.homepage = %q{http://github.com/thumblemonks/riot} 47 | s.rdoc_options = ["--charset=UTF-8"] 48 | s.require_paths = ["lib"] 49 | s.rubygems_version = %q{1.3.5} 50 | s.summary = %q{An extremely fast, expressive, and context-driven unit-testing framework. Protest the slow test.} 51 | s.test_files = [ 52 | "test/assertion_macros/assertion_macro_assigns_test.rb", 53 | "test/assertion_macros/assertion_macro_equals_test.rb", 54 | "test/assertion_macros/assertion_macro_exists_test.rb", 55 | "test/assertion_macros/assertion_macro_kind_of_test.rb", 56 | "test/assertion_macros/assertion_macro_matching_test.rb", 57 | "test/assertion_macros/assertion_macro_nil_test.rb", 58 | "test/assertion_macros/assertion_macro_raises_test.rb", 59 | "test/assertion_macros/assertion_macro_respond_to_test.rb", 60 | "test/assertion_test.rb", 61 | "test/benchmark/riot_vs_minitest.rb", 62 | "test/benchmark/simple_context_and_assertions.rb", 63 | "test/context_test.rb", 64 | "test/teststrap.rb" 65 | ] 66 | 67 | if s.respond_to? :specification_version then 68 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 69 | s.specification_version = 3 70 | 71 | if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then 72 | else 73 | end 74 | else 75 | end 76 | end 77 | 78 | -------------------------------------------------------------------------------- /test/context_test.rb: -------------------------------------------------------------------------------- 1 | require 'teststrap' 2 | 3 | context "any context" do 4 | setup do 5 | @reporter = Riot::NilReport.new 6 | @context = Riot::Context.new("a", @reporter) 7 | end 8 | 9 | context "that doesn't have passing tests" do 10 | setup do 11 | @context.should("a") { true } 12 | @context.should("b") { false } 13 | @context.should("c") { raise Exception, "blah" } 14 | @context.report 15 | end 16 | 17 | asserts("passed test count") { @reporter.passes }.equals(1) 18 | asserts("failure count") { @reporter.failures }.equals(1) 19 | asserts("unexpected errors count") { @reporter.errors }.equals(1) 20 | end # that doesn't have passing tests 21 | 22 | context "when running setup:" do 23 | setup { @context.setup { "foo" } } 24 | 25 | asserts "topic becomes available to test as result of setup" do 26 | @context.should("bar") { topic }.actual 27 | end.equals("foo") 28 | 29 | asserts "calling topic in context will return assertion that returns topic as the actual" do 30 | @context.topic.actual 31 | end.equals("foo") 32 | end # when running setup 33 | end # any context 34 | 35 | # 36 | # Basic Context 37 | 38 | context "basic context" do 39 | setup do 40 | test_context = Riot::Context.new("foo", Riot::NilReport.new) 41 | test_context.setup { @test_counter = 0 } 42 | test_context.asserts("truthiness") { @test_counter += 1; true } 43 | test_context.asserts("more truthiness") { @test_counter += 1; true } 44 | test_context 45 | end 46 | 47 | asserts("context description") { topic.to_s }.equals("foo") 48 | asserts("assertion count") { topic.assertions.length }.equals(2) 49 | should("call setup once per context") { topic.situation }.assigns(:test_counter, 2) 50 | end # basic context 51 | 52 | # 53 | # Nested Context 54 | 55 | context "nested context" do 56 | setup do 57 | test_context = Riot::Context.new("foo", Riot::NilReport.new) 58 | test_context.setup { @test_counter = 0; @foo = "bar" } 59 | test_context.asserts("truthiness") { @test_counter += 1; true } 60 | test_context 61 | end 62 | 63 | context "inner context with own setup" do 64 | setup do 65 | test_context = topic.context("baz") 66 | test_context.setup { @test_counter += 10 } 67 | test_context 68 | end 69 | 70 | should("inherit parent context") { topic.situation }.assigns(:test_counter, 10) 71 | should("chain context names") { topic.to_s }.equals("foo baz") 72 | end 73 | 74 | context "inner context without its own setup" do 75 | setup { topic.context("bum") } 76 | asserts("parent setup is called") { topic.situation }.assigns(:foo, "bar") 77 | end 78 | end 79 | 80 | # 81 | # Multiple setups in a context 82 | 83 | context "multiple setups" do 84 | setup do 85 | test_context = Riot::Context.new("foo", Riot::NilReport.new) 86 | test_context.setup { @foo = "bar" } 87 | test_context.setup { @baz = "boo" } 88 | test_context 89 | end 90 | 91 | asserts("foo") { topic.situation }.assigns(:foo, "bar") 92 | asserts("bar") { topic.situation }.assigns(:baz, "boo") 93 | 94 | context "in the parent of a nested context" do 95 | setup do 96 | test_context = topic.context("goo") 97 | test_context.setup { @goo = "car" } 98 | test_context 99 | end 100 | 101 | asserts("foo") { topic.situation }.assigns(:foo, "bar") 102 | asserts("bar") { topic.situation }.assigns(:baz, "boo") 103 | asserts("goo") { topic.situation }.assigns(:goo, "car") 104 | end # in the parent of a nested context 105 | end # multiple setups 106 | -------------------------------------------------------------------------------- /lib/riot/assertion_macros.rb: -------------------------------------------------------------------------------- 1 | module Riot 2 | module AssertionMacros 3 | # Asserts that the result of the test equals the expected value 4 | # asserts("test") { "foo" }.equals("foo") 5 | # should("test") { "foo" }.equals("foo") 6 | def equals(expected) 7 | expected == actual || fail("expected #{expected.inspect}, not #{actual.inspect}") 8 | end 9 | 10 | # Asserts that the result of the test is nil 11 | # asserts("test") { nil }.nil 12 | # should("test") { nil }.nil 13 | def nil 14 | actual.nil? || fail("expected nil, not #{actual.inspect}") 15 | end 16 | 17 | # Asserts that the result of the test is a non-nil value. This is useful in the case where you don't want 18 | # to translate the result of the test into a boolean value 19 | # asserts("test") { "foo" }.exists 20 | # should("test") { 123 }.exists 21 | # asserts("test") { "" }.exists 22 | # asserts("test") { nil }.exists # This would fail 23 | def exists 24 | !actual.nil? || fail("expected a non-nil value") 25 | end 26 | 27 | # Asserts that the test raises the expected Exception 28 | # asserts("test") { raise My::Exception }.raises(My::Exception) 29 | # should("test") { raise My::Exception }.raises(My::Exception) 30 | # 31 | # You can also check to see if the provided message equals or matches your expectations. The message from 32 | # the actual raised exception will be converted to a string before any comparison is executed. 33 | # asserts("test") { raise My::Exception, "Foo" }.raises(My::Exception, "Foo") 34 | # asserts("test") { raise My::Exception, "Foo Bar" }.raises(My::Exception, /Bar/) 35 | # asserts("test") { raise My::Exception, ["a", "b"] }.raises(My::Exception, "ab") 36 | def raises(expected, expected_message=nil) 37 | actual_error = raised && raised.original_exception 38 | @raised = nil 39 | if actual_error.nil? 40 | return fail("should have raised #{expected}, but raised nothing") 41 | elsif expected != actual_error.class 42 | return fail("should have raised #{expected}, not #{actual_error.class}") 43 | elsif expected_message && !(actual_error.message.to_s =~ %r[#{expected_message}]) 44 | return fail("expected #{expected_message} for message, not #{actual_error.message}") 45 | end 46 | true 47 | end 48 | 49 | # Asserts that the result of the test equals matches against the proved expression 50 | # asserts("test") { "12345" }.matches(/\d+/) 51 | # should("test") { "12345" }.matches(/\d+/) 52 | def matches(expected) 53 | expected = %r[#{Regexp.escape(expected)}] if expected.kind_of?(String) 54 | actual =~ expected || fail("expected #{expected.inspect} to match #{actual.inspect}") 55 | end 56 | 57 | # Asserts that the result of the test is an object that is a kind of the expected type 58 | # asserts("test") { "foo" }.kind_of(String) 59 | # should("test") { "foo" }.kind_of(String) 60 | def kind_of(expected) 61 | actual.kind_of?(expected) || fail("expected kind of #{expected}, not #{actual.inspect}") 62 | end 63 | 64 | # Asserts that the result of the test is an object that responds to the given method 65 | # asserts("test") { "foo" }.respond_to(:to_s) 66 | # should("test") { "foo" }.respond_to(:to_s) 67 | def respond_to(expected) 68 | actual.respond_to?(expected) || fail("expected method #{expected.inspect} is not defined") 69 | end 70 | 71 | # Asserts that an instance variable is defined for the result of the assertion. Value of instance 72 | # variable is expected to not be nil 73 | # setup { User.new(:email => "foo@bar.baz") } 74 | # topic.assigns(:email) 75 | # 76 | # If a value is provided in addition to the variable name, the actual value of the instance variable 77 | # must equal the expected value 78 | # setup { User.new(:email => "foo@bar.baz") } 79 | # topic.assigns(:email, "foo@bar.baz") 80 | def assigns(variable, expected_value=nil) 81 | actual_value = actual.instance_variable_get("@#{variable}") 82 | return fail("expected @#{variable} to be assigned a value") if actual_value.nil? 83 | unless expected_value.nil? || expected_value == actual_value 84 | return fail(%Q[expected @#{variable} to be equal to '#{expected_value}', not '#{actual_value}']) 85 | end 86 | true 87 | end 88 | end # AssertionMacros 89 | end # Riot 90 | 91 | Riot::Assertion.instance_eval { include Riot::AssertionMacros } -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Riot 2 | 3 | An extremely fast, expressive, and context-driven unit-testing framework. Protest the slow test. 4 | 5 | #### Installation 6 | 7 | The Riot gem is hosted on gemcutter.org. It used to be hosted on GitHub, but they turned of gem support while moving to Rackspace, so we moved to gemcutter. If you have not already done so, add gemcutter.org to your list of gem sources like so: 8 | 9 | sudo gem sources -a http://gemcutter.org 10 | 11 | Then, simply install the Riot gem like so: 12 | 13 | sudo gem install riot 14 | 15 | #### Note on speed 16 | 17 | I have done a really simple benchmarking (10,000 runs), but right now, Riot is running about **2 times** faster than Test::unit and thusly Shoulda: 18 | 19 | Rehearsal ---------------------------------------------- 20 | Riot 0.620000 0.010000 0.630000 ( 0.647318) 21 | Test::Unit 1.500000 0.010000 1.510000 ( 1.508144) 22 | Shoulda 1.490000 0.000000 1.490000 ( 1.501468) 23 | ------------------------------------- total: 3.630000sec 24 | 25 | user system total real 26 | Riot 0.630000 0.010000 0.640000 ( 0.640481) 27 | Test::Unit 1.460000 0.000000 1.460000 ( 1.476454) 28 | Shoulda 1.480000 0.010000 1.490000 ( 1.490633) 29 | 30 | "Is it valid?", you ask. *I* think it is. I ain't no cheater, but I might be delusional. 31 | 32 | To compare against MiniTest, I had to run the benchmark separately. 33 | 34 | Rehearsal -------------------------------------------- 35 | Riot 0.630000 0.010000 0.640000 ( 0.641285) 36 | MiniTest 0.770000 0.070000 0.840000 ( 0.846447) 37 | ----------------------------------- total: 1.480000sec 38 | 39 | user system total real 40 | Riot 0.630000 0.000000 0.630000 ( 0.632337) 41 | MiniTest 0.730000 0.070000 0.800000 ( 0.798107) 42 | 43 | Riot is currently only slightly faster, but I haven't done any optimization yet. Riot is also half the code of MiniTest (`313 loc < 674 loc` :) 44 | 45 | All tests ran with `ruby 1.8.7 (2009-06-12 patchlevel 174) [i686-darwin9]`. 46 | 47 | ## Examples 48 | 49 | #### Example: Basic booleans 50 | 51 | **NOTE:** For no specific reason, I'm going to use an ActiveRecord model in the following examples. 52 | 53 | At it's very basic, Riot simply tries to assert that an expression is true or false. Riot does this through its `asserts` or `should` tests. An `asserts` test will pass if the result of running the test is neither `nil` or `false`. A `should` test does the exact same thing - they are in fact aliases. The only difference is in the way you write the assertion. 54 | 55 | For instance, given a test file named `foo_test.rb`, you might have the following code in it: 56 | 57 | require 'riot' 58 | 59 | context "a new user" do 60 | setup { User.new } 61 | asserts("that it is not yet created") { topic.new_record? } 62 | end 63 | 64 | Notice that you do not define a class anywhere. That would be the entire contents of that test file. If you wanted to use a `should` instead, you could say this: 65 | 66 | require 'riot' 67 | 68 | context "a new user" do 69 | setup { User.new } 70 | should("not be created") { topic.new_record? } 71 | end 72 | 73 | Sometimes it's more clear to say "this **should** be that" and sometimes it's better to say "**asserts** this is that". I promise you that Riot will get no more redundant than this, but also that besides speed, Riot will aim at being expressive with a minimal amount of syntax. 74 | 75 | The other important thing to note in the examples above is the use of the `topic`. Calling `topic` within any assertion will actually return the value of whatever was evaluated and returned from calling setup in the given context. In the examples above, `User.new` was returned, and is therefor accessible as the `topic`. 76 | 77 | I'm going to use `asserts` for the rest of this introduction, but you should know that you can replace any instance of `asserts` with `should` and nothing would change. 78 | 79 | #### Example: Shortcut - Asserting the topic itself 80 | 81 | Over the course of developing Riot it became somewhat obvious to some of us that we were creating assertions that returned the `topic` just so we could assert things about the topic itself. For instance, were doing this: 82 | 83 | context "a billionaire" do 84 | setup { MoneyMaker.build(:billionaire) } 85 | 86 | should("be a Billionaire") { topic }.kind_of(Billionaire) 87 | end 88 | 89 | This is awfully redundant - not to mention, contrived. So, we wrote a shortcut to generate an assertion that returns topic. This means we can now do this: 90 | 91 | context "a billionaire" do 92 | setup { MoneyMaker.build(:billionaire) } 93 | topic.kind_of(Billionaire) 94 | end 95 | 96 | #### Example: Equality 97 | 98 | One of the most common assertions you will (or do already) utilize is that of equality; is this equal to that? Riot supports this in a slightly different manner than most other frameworks. With Riot, you add the expectation to the assertion itself. 99 | 100 | For example: 101 | 102 | context "a new user" do 103 | setup { User.new(:email => 'foo@bar.com') } 104 | asserts("email address") { topic.email }.equals('foo@bar.com') 105 | end 106 | 107 | Here, you should begin to notice that tests themselves return the actual value. You do not write assertions into the test. Assertions are "aspected" onto the test. If the test above did not return 'foo@bar.com' for `@user.email`, the assertion would have failed. 108 | 109 | The `equals` modifier works with any type of value, including nil's. However, if you wanted to test for nil explicitly, you could simply do this: 110 | 111 | context "a new user" do 112 | setup { User.new } 113 | asserts("email address") { topic.email }.nil 114 | end 115 | 116 | Notice the `nil` modifier added to asserts. Also notice how the test almost reads as "a new user asserts email address *is* nil". With Test::Unit, you would have probably written: 117 | 118 | class UserTest < Test::Unit::TestCase 119 | def setup 120 | @user = User.new 121 | end 122 | 123 | def test_email_address_is_nil 124 | assert_nil @user.email 125 | end 126 | end 127 | 128 | Which, to me, seems like a redundancy. The test already says it's nil! Maybe Shoulda woulda helped: 129 | 130 | class UserTest < Test::Unit::TestCase 131 | def setup 132 | @user = User.new 133 | end 134 | 135 | should "have nil email" { assert_nil @user.email } 136 | end 137 | 138 | In my opinion, the same redundancy exists. Sure, I could write a macro like `should_be_nil {@user.email}`, but the redundancy exists in the framework itself. 139 | 140 | #### Example: Matches 141 | 142 | If you need to assert that a test result matches a regular expression, use the `matches` modifier like so: 143 | 144 | context "a new user" do 145 | setup { User.new } 146 | 147 | # I'm a contrived example 148 | asserts("random phone number") { topic.random_phone_number }.matches(/^\d{3}-\d{3}-\d{4}$/) 149 | end 150 | 151 | #### Example: Raises 152 | 153 | Sometimes, your test raises an exception that you actually expected. 154 | 155 | context "a new user" do 156 | setup { User.new } 157 | asserts("invalid data") { topic.save! }.raises(ActiveRecord::RecordInvalid) 158 | end 159 | 160 | And if you wanted to check that the exception and message match what you expect: 161 | 162 | context "a new user" do 163 | setup { User.new } 164 | asserts("invalid data") { topic.save! }.raises(ActiveRecord::RecordInvalid, /has errors/) 165 | end 166 | 167 | #### Example: Kind Of 168 | 169 | When you want to test that an expression returns an object of an expected type: 170 | 171 | context "a new user" do 172 | setup { User.new } 173 | asserts("its balance") { topic.balance }.kind_of(Currency) 174 | end 175 | 176 | #### Example: Respond To 177 | 178 | When you want to test that an object responds to a specific method: 179 | 180 | context "a new user" do 181 | setup { User.new } 182 | asserts("email is defined") { topic }.respond_to(:email) 183 | end 184 | 185 | #### Example: Assigns 186 | 187 | Sometimes you want to check and see if an instance variable is defined. 188 | 189 | context "a new user" do 190 | setup do 191 | User.new(:email => "foo@bar.baz", :first_name => nil) 192 | end 193 | asserts("has an email address") { topic }.assigns(:email) 194 | asserts("has a first name") { topic }.assigns(:first_name) # This will fail 195 | end 196 | 197 | While other times you also want to make sure the value of the instance variable is what you expect it to be. 198 | 199 | context "a new user" do 200 | setup do 201 | User.new(:email => "foo@bar.baz", :first_name => "John") 202 | end 203 | asserts("has an email address") { topic }.assigns(:email, "foo@bar.baz") 204 | asserts("has a first name") { topic }.assigns(:first_name, "Joe") # This will fail 205 | end 206 | 207 | #### Example: Nested contexts 208 | 209 | Oh yeah, Riot does those, too. The `setup` from each parent is "loaded" into the context and then the context is executed as normal. Test naming is a composite of the parent contexts' names. Here, we'll do a little Sinatra testing (see below for instructions on how to make it Riot work seamlessly with Sinatra tests). 210 | 211 | context "My App:" do 212 | setup { @app = MyApp } 213 | 214 | context "get /" do 215 | setup { get '/' } 216 | # ... 217 | # assertions 218 | 219 | context "renders a body" do 220 | setup { @body = last_response.body } 221 | # ... 222 | # assertions 223 | end 224 | end 225 | 226 | context "get /books/1" do 227 | setup { get '/books/1' } 228 | # ... 229 | # assertions 230 | end 231 | end 232 | 233 | #### More examples/documentation 234 | 235 | There are many more basic assertion modifiers to come. See below for writing Riot extensions if you want to help out. 236 | 237 | See the TODO section for everything that's missing. 238 | 239 | Also, see [the wiki](http://wiki.github.com/thumblemonks/riot) for more examples and documentation. 240 | 241 | ## You say, "OMG! Why did you write this?" 242 | 243 | ### Some background, I guess 244 | 245 | You start a new project. You get all excited. You're adding tests. You're adding factories. You're fixturating your setups. You're adding more tests. Your tests start slowing down, but you need to keep pushing because your backlog has a lot of new, nifty features in it. You've got 3000+ lines of test code, 2000+ assertions. Your tests are annoyingly slow. Your tests have become a burden. 246 | 247 | I hate this and it happens a lot, even when I'm conscience that it's happening. Even when I'm not hitting the database and I'm mocking the crap out my code. 248 | 249 | I really, really hate slow test suites. 250 | 251 | #### Did ... you look at Shoulda 252 | 253 | I should say that I love Shoulda in theory and in practice. It changed the way I coded. I added macros all over the place. I built macros into the gems I wrote for the gem itself. But, alas, Shoulda is slow. Shoulda is based on Test::Unit. Shoulda reruns setups for every should. Shoulda could make my life even easier with even more expressiveness. 254 | 255 | #### Did ... you look at RSpec 256 | 257 | :| yes, no, I don't care. It's slow, too. Anyways, I was at [ObjectMentor](http://objectmentor.com) many, many moons ago when Dave Astels (accompanied by David Chelimsky) were explaining this brand new approach to testing called BDD. Mr. Astels explained to us that we if we already understood TDD, then BDD wouldn't help a bunch. Why argue with him? 258 | 259 | ### How Riot is the same 260 | 261 | 1. It defines a context 262 | 1. It prints .'s, F's, and E's when tests pass, fail, or error 263 | 1. It tells you how long it took to run just the tests 264 | 1. Contexts can be nested and setups inherited 265 | 266 | ### How Riot is different 267 | 268 | Riot differs primarily in that it does not rerun setup for each test in a context. I know this is going to shock and awe a lot of folks. However, over the past several years of my doing TDD in some capacity or another, there are certain habits I have tried to pick up on any many others I have tried to drop. 269 | 270 | For instance, I believe that no assertion should mangle the context of the test data it is running in. Following this allows me to require setup be run only once for a collection of related assertions. Even in a nested context where setups are inherited, the setup's are called only once per the specific context. 271 | 272 | Following all of this allows me to have very fast tests (so far). 273 | 274 | ... 275 | 276 | Riot is also different in that assertions are not added to the test block. Each test block is it's own assertion (and assertion block). Whatever the result is of processing an assertion block will be used to determine if an assertion passed or failed. Each assertion block can have a specific validator tied to it for asserting any number of things, like: the result of the test **equals** some expected value, the result of the test **matches** some expected expression, or the result of the test **raises** some exception. 277 | 278 | I like this approach because I only want to test one thing, but that one thing may require some work on my behalf to get the value. Riot does not let me cheat in this regard. There is no way for me to add more than one assertion to an assertion block. 279 | 280 | ... 281 | 282 | I imagine this approach will persuade many of you to avoid Riot altogether. I don't blame you. A few years ago I would have avoided it, too. As of this writing though, I have ported Chicago and Slvu (which were previously written in Test::Unit + Shoulda) to Riot, cut the number of lines of code in almost half, definitely more than halved the amount of time the tests took to run, and did so in less than half a day (I was building Riot while porting them :). 283 | 284 | ## Running tests 285 | 286 | Create or modify your existing Rakefile to define a test task like so: 287 | 288 | desc 'Default task: run all tests' 289 | task :default => [:test] 290 | 291 | require 'rake/testtask' 292 | Rake::TestTask.new(:test) do |test| 293 | test.libs << 'lib' << 'test' 294 | test.pattern = 'test/**/*_test.rb' 295 | test.verbose = false 296 | end 297 | 298 | Basically, it's like setting up any other test runner. Then, from the command line, you only need to run `rake` or `rake test`. Please make sure to remove all references to any other testing frameworks before running tests. For instance, do not require `test/unit`, `shoulda`, `minitest`, or anything else like it. 299 | 300 | ### With Sinatra 301 | 302 | Riot definitely works with the latest Sinatra. I personally use it to run tests for [Chicago](http://github.com/thumblemonks/chicago) and [Slvu](http://github.com/jaknowlden/slvu). Setup is pretty easy and very much like getting your tests to run with Test::Unit. In a test helper file that gets loaded into all of your tests (that need it), enter the following: 303 | 304 | require 'riot' 305 | class Riot::Context 306 | include Rack::Test::Methods 307 | def app; @app; end 308 | end 309 | 310 | And then define a context (or many) for testing your Sinatra app. For instance: 311 | 312 | require 'test_helper' 313 | 314 | context 'Some App' do 315 | setup { @app = SomeApp } 316 | 317 | context "/index" do 318 | setup { get '/' } 319 | # ... 320 | end 321 | end 322 | 323 | Make sure to check out the Riot + Sinatra testing macros in Chicago. 324 | 325 | ### With Rails 326 | 327 | It's doubtful that Riot works with Rails very easily as Riot completely replaces Test::Unit. I haven't tried it yet, but intend to with my next new Rails project. Porting would probably take some time unless you only have a few test cases. Porting is made somewhat easier if you're already using Shoulda; you can replace the `TestCase` definition with a `context` of the same name as the class under test I suppose. 328 | 329 | ## Extending Riot with Macros 330 | 331 | To extend Riot, similar to how you would with Shoulda, you simply need to include your methods into the `Riot::Context` class. For example, let's say you wanted to add a helpful macro for asserting the response status of some HTTP result in Sinatra. You could do this easily by defining your macro like so: 332 | 333 | module Custom 334 | module Macros 335 | def asserts_response_status(expected) 336 | asserts("response status is #{expected}") do 337 | last_response.status 338 | end.equals(expected) 339 | end 340 | end # Macros 341 | end # Custom 342 | Riot::Context.instance_eval { include Custom::Macros } 343 | 344 | And then in your actual test, you might do the following: 345 | 346 | context 'Some App' do 347 | setup { @app = SomeApp } 348 | 349 | context "/index" do 350 | setup { get '/' } 351 | asserts_response_status 200 352 | end 353 | end 354 | 355 | **COMING SOON:** Riot will look into test/riot\_macros, but not today. 356 | 357 | #### Assertion macros 358 | 359 | If you want to add special macros to an Assertion, this is as easy as adding them to a Context. Similar to Context macros, Assertion macros are included into the Assertion class. 360 | 361 | For instance, let's say you wanted to add a macro for verifying that the result of an assertion is the same kind\_of object as you would expect. You would define the macro like so: 362 | 363 | module Custom 364 | module AssertionMacros 365 | def kind_of(expected_class) 366 | actual.kind_of?(expected) || fail("expected kind of #{expected}, not #{actual.inspect}") 367 | end 368 | end # AssertionMacros 369 | end # Custom 370 | Riot::Assertion.instance_eval { include Custom::AssertionMacros } 371 | 372 | And in your context, you would use it like so: 373 | 374 | context "example" do 375 | asserts("a hash is defined") { {:foo => 'bar'} }.kind_of(Hash) 376 | end 377 | 378 | Notice in the new macro we defined the use of the magical **actual** variable. `actual` is evaluated when the assertion is defined and made available to any Assertion macro. If you think you might want to chain assertions checks together, know that only the last failure will get recorded. 379 | 380 | ## TODO 381 | 382 | TONS OF STUFF 383 | 384 | 1. Documentation about the `topic` method 385 | 1. Better documentation for everything 386 | 1. Refactor reporting; some abstracting is needed for recording a result (for instance) 387 | 1. Need to know where in backtrace a test failed (line number, etc.); i.e. backtrace filtering for clarity 388 | 1. More assertion macros: throws, etc. 389 | 1. Aliases for context "with, without, when, ..."; add those words to test description 390 | 1. Optimization and simplification (ex. flog is complaining print\_result\_stack) 391 | 1. Better error messages? 392 | 1. Perhaps: Multiple setup blocks in one context 393 | 1. Perhaps: association macro chaining 394 | 1. Perhaps: Uhhhhh ... a teardown method (only maybe. not sold :) 395 | --------------------------------------------------------------------------------