├── .gitignore ├── LICENSE ├── README.markdown ├── Rakefile ├── contest.gemspec ├── contest.gemspec.erb ├── lib └── contest.rb ├── rails └── init.rb └── test ├── all_test.rb └── setup_and_teardown_order_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Damian Janowski and Michel Martens for Citrusbyte 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Contest 2 | ======= 3 | 4 | Contexts for Test::Unit. 5 | 6 | Description 7 | ----------- 8 | 9 | Write declarative tests using nested contexts without performance penalties. Contest is less than 100 lines of code and gets the job done. 10 | 11 | Usage 12 | ----- 13 | 14 | Declare your tests as you would in RSpec or Shoulda: 15 | 16 | require 'contest' 17 | 18 | class SomeTest < Test::Unit::TestCase 19 | setup do 20 | @value = 1 21 | end 22 | 23 | teardown do 24 | @value = nil 25 | end 26 | 27 | test "sample test" do 28 | assert_equal 1, @value 29 | end 30 | 31 | context "a context" do 32 | setup do 33 | @value += 1 34 | end 35 | 36 | test "more tests" do 37 | assert_equal 2, @value 38 | end 39 | 40 | context "a nested context" do 41 | setup do 42 | @value += 1 43 | end 44 | 45 | test "yet more tests" do 46 | assert_equal 3, @value 47 | end 48 | end 49 | end 50 | end 51 | 52 | For your convenience, `context` is aliased as `describe` and `test` is aliased as `should`, so this is valid: 53 | 54 | class SomeTest < Test::Unit::TestCase 55 | setup do 56 | @value = 1 57 | end 58 | 59 | describe "something" do 60 | setup do 61 | @value += 1 62 | end 63 | 64 | should "equal 2" do 65 | assert_equal 2, @value 66 | end 67 | end 68 | end 69 | 70 | You can run it normally, it's Test::Unit after all. If you want to run a particular test, say "yet more tests", try this: 71 | 72 | $ testrb my_test.rb -n test_yet_more_tests 73 | 74 | Or with a regular expression: 75 | 76 | $ testrb my_test.rb -n /yet_more_tests/ 77 | 78 | Installation 79 | ------------ 80 | 81 | $ sudo gem install contest 82 | 83 | If you want to use it with Rails, add this to config/environment.rb: 84 | 85 | config.gem "contest" 86 | 87 | Then you can vendor the gem: 88 | 89 | rake gems:install 90 | rake gems:unpack 91 | 92 | License 93 | ------- 94 | 95 | Copyright (c) 2009 Damian Janowski and Michel Martens for Citrusbyte 96 | 97 | Permission is hereby granted, free of charge, to any person 98 | obtaining a copy of this software and associated documentation 99 | files (the "Software"), to deal in the Software without 100 | restriction, including without limitation the rights to use, 101 | copy, modify, merge, publish, distribute, sublicense, and/or sell 102 | copies of the Software, and to permit persons to whom the 103 | Software is furnished to do so, subject to the following 104 | conditions: 105 | 106 | The above copyright notice and this permission notice shall be 107 | included in all copies or substantial portions of the Software. 108 | 109 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 110 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 111 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 112 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 113 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 114 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 115 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 116 | OTHER DEALINGS IN THE SOFTWARE. 117 | 118 | ## About Citrusbyte 119 | 120 | ![Citrusbyte](http://i.imgur.com/W6eISI3.png) 121 | 122 | Contest is lovingly maintained and funded by Citrusbyte. 123 | At Citrusbyte, we specialize in solving difficult computer science problems for startups and the enterprise. 124 | 125 | At Citrusbyte we believe in and support open source software. 126 | * Check out more of our open source software at Citrusbyte Labs. 127 | * Learn more about [our work](https://citrusbyte.com/portfolio). 128 | * [Hire us](https://citrusbyte.com/contact) to work on your project. 129 | * [Want to join the team?](http://careers.citrusbyte.com) 130 | 131 | *Citrusbyte and the Citrusbyte logo are trademarks or registered trademarks of Citrusbyte, LLC.* 132 | 133 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/testtask' 3 | 4 | task :default => :test 5 | 6 | Rake::TestTask.new(:test) do |t| 7 | t.pattern = 'test/**/*_test.rb' 8 | t.verbose = false 9 | end 10 | -------------------------------------------------------------------------------- /contest.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'contest' 3 | s.version = '0.1.3' 4 | s.summary = %{Write more readable tests in Test::Unit with this tiny script.} 5 | s.description = %{Write declarative tests using nested contexts without performance penalties. Contest is less than 100 lines of code and gets the job done.} 6 | s.authors = ["Damian Janowski", "Michel Martens"] 7 | s.email = ["djanowski@dimaion.com", "michel@soveran.com"] 8 | s.homepage = "http://github.com/citrusbyte/contest" 9 | s.files = ["lib/contest.rb", "README.markdown", "LICENSE", "Rakefile", "rails/init.rb", "test/all_test.rb", "test/setup_and_teardown_order_test.rb"] 10 | s.rubyforge_project = "contest" 11 | end 12 | 13 | -------------------------------------------------------------------------------- /contest.gemspec.erb: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'contest' 3 | s.version = '0.1.3' 4 | s.summary = %{Write more readable tests in Test::Unit with this tiny script.} 5 | s.description = %{Write declarative tests using nested contexts without performance penalties. Contest is less than 100 lines of code and gets the job done.} 6 | s.authors = ["Damian Janowski", "Michel Martens"] 7 | s.email = ["djanowski@dimaion.com", "michel@soveran.com"] 8 | s.homepage = "http://github.com/citrusbyte/contest" 9 | s.files = <%= Dir['lib/**/*.rb', 'README*', 'LICENSE', 'Rakefile', 'rails/**/*', 'test/**/*.*'].inspect %> 10 | s.rubyforge_project = "contest" 11 | end 12 | 13 | -------------------------------------------------------------------------------- /lib/contest.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | 3 | # Test::Unit loads a default test if the suite is empty, whose purpose is to 4 | # fail. Since having empty contexts is a common practice, we decided to 5 | # overwrite TestSuite#empty? in order to allow them. Having a failure when no 6 | # tests have been defined seems counter-intuitive. 7 | class Test::Unit::TestSuite 8 | def empty? 9 | false 10 | end 11 | end 12 | 13 | # Contest adds +teardown+, +test+ and +context+ as class methods, and the 14 | # instance methods +setup+ and +teardown+ now iterate on the corresponding 15 | # blocks. Note that all setup and teardown blocks must be defined with the 16 | # block syntax. Adding setup or teardown instance methods defeats the purpose 17 | # of this library. 18 | class Test::Unit::TestCase 19 | def self.setup(&block) 20 | define_method :setup do 21 | super(&block) 22 | instance_eval(&block) 23 | end 24 | end 25 | 26 | def self.teardown(&block) 27 | define_method :teardown do 28 | instance_eval(&block) 29 | super(&block) 30 | end 31 | end 32 | 33 | def self.context(*name, &block) 34 | subclass = Class.new(self) 35 | remove_tests(subclass) 36 | subclass.class_eval(&block) if block_given? 37 | const_set(context_name(name.join(" ")), subclass) 38 | end 39 | 40 | def self.test(name, &block) 41 | define_method(test_name(name), &block) 42 | end 43 | 44 | class << self 45 | alias_method :should, :test 46 | alias_method :describe, :context 47 | end 48 | 49 | private 50 | 51 | def self.context_name(name) 52 | "Test#{sanitize_name(name).gsub(/(^| )(\w)/) { $2.upcase }}".to_sym 53 | end 54 | 55 | def self.test_name(name) 56 | "test_#{sanitize_name(name).gsub(/\s+/,'_')}".to_sym 57 | end 58 | 59 | def self.sanitize_name(name) 60 | name.gsub(/\W+/, ' ').strip 61 | end 62 | 63 | def self.remove_tests(subclass) 64 | subclass.public_instance_methods.grep(/^test_/).each do |meth| 65 | subclass.send(:undef_method, meth.to_sym) 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /rails/init.rb: -------------------------------------------------------------------------------- 1 | # ActiveStupor defines its own idiom for the class-level setup method 2 | # (using callback chains). This hack is to ensure that Contest users can 3 | # still call the setup method with a block. 4 | if RAILS_ENV == 'test' 5 | require File.join(File.dirname(__FILE__), '..', 'lib', 'contest') 6 | require "active_support/test_case" 7 | 8 | class Test::Unit::TestCase 9 | class << self 10 | alias contest_setup setup 11 | end 12 | end 13 | 14 | class ActiveSupport::TestCase 15 | class << self 16 | alias activesupport_setup setup 17 | end 18 | 19 | def self.setup(*args, &block) 20 | if args.empty? 21 | contest_setup(&block) 22 | else 23 | activesupport_setup(*args) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/all_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.join("..", "lib", "contest"), 2 | File.dirname(__FILE__)) 3 | 4 | class FooTest < Test::Unit::TestCase 5 | setup do 6 | @value = 1 7 | end 8 | 9 | teardown do 10 | @value = nil 11 | end 12 | 13 | test "truth" do 14 | assert_equal 1, @value 15 | end 16 | 17 | context "context's non-word characters " do 18 | should "run the test inside" do 19 | assert_equal 1, @value 20 | end 21 | end 22 | 23 | context "context", "with multiple", "arguments", 123, FooTest do 24 | should "run the test inside" do 25 | assert_equal 1, @value 26 | end 27 | end 28 | 29 | context "some context" do 30 | setup do 31 | @value += 1 32 | end 33 | 34 | test "another truth" do 35 | assert_equal 2, @value 36 | end 37 | 38 | context "and a nested context" do 39 | setup do 40 | @value += 1 41 | end 42 | 43 | test "more" do 44 | assert_equal 3, @value 45 | end 46 | end 47 | end 48 | 49 | context "some other context" do 50 | setup do 51 | @value += 1 52 | end 53 | 54 | test "yet another truth" do 55 | assert_equal 2, @value 56 | end 57 | end 58 | 59 | describe "context with should" do 60 | setup do 61 | @value += 1 62 | end 63 | 64 | should "yet another truth" do 65 | assert_equal 2, @value 66 | end 67 | end 68 | end 69 | 70 | class BarTest < Test::Unit::TestCase 71 | setup do 72 | @value = 1 73 | end 74 | 75 | context "some context" do 76 | setup do 77 | @value += 1 78 | end 79 | 80 | test "another truth" do 81 | assert_equal 2, @value 82 | end 83 | 84 | test "yet another truth" do 85 | assert_equal 2, @value 86 | end 87 | end 88 | end 89 | 90 | class TestBaz < Test::Unit::TestCase 91 | def foo 92 | 42 93 | end 94 | 95 | def setup 96 | @value = 1 97 | super 98 | end 99 | 100 | context "some context" do 101 | def setup 102 | super 103 | @value += 2 104 | end 105 | 106 | def bar 107 | foo + 1 108 | end 109 | 110 | test "a helper" do 111 | assert_equal 42, foo 112 | assert_equal 3, @value 113 | end 114 | 115 | test "another helper" do 116 | assert_equal 43, bar 117 | end 118 | 119 | context "another context" do 120 | setup do 121 | @value += 3 122 | end 123 | 124 | test "blah" do 125 | assert_equal 6, @value 126 | end 127 | end 128 | end 129 | 130 | context "empty context" 131 | end 132 | -------------------------------------------------------------------------------- /test/setup_and_teardown_order_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.join("..", "lib", "contest"), 2 | File.dirname(__FILE__)) 3 | 4 | class BaseTest < Test::Unit::TestCase 5 | def setup 6 | @order = [] 7 | @order << "Grandparent Setup" 8 | end 9 | 10 | def teardown 11 | @order << "Grandparent Teardown" 12 | 13 | assert_equal ["Grandparent Setup", "Parent Setup", "Child Setup", "Test Case", "Child Teardown", "Parent Teardown", "Grandparent Teardown"], @order 14 | end 15 | end 16 | 17 | class MidLayerTest < BaseTest 18 | setup { @order << "Parent Setup" } 19 | teardown { @order << "Parent Teardown" } 20 | end 21 | 22 | class LeafTest < MidLayerTest 23 | setup { @order << "Child Setup" } 24 | teardown { @order << "Child Teardown" } 25 | 26 | test "my actual test" do 27 | @order << "Test Case" 28 | end 29 | end 30 | --------------------------------------------------------------------------------