This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Helps to organize and structure your tests with | |
# * human readable test names | |
# * flagging tests as pending | |
# * structuring the test implementation with blocks | |
# Please see the documentation of single methods below. | |
# Written by Vladimir Dobriakov | |
# See http://blog.geekq.net/2009/11/25/minimalist-testing-ruby/ for explanation | |
require 'test/unit' | |
class << Test::Unit::TestCase | |
attr_writer :ignore_failures | |
attr_writer :human_test_names | |
def ignore_failures | |
@ignore_failures ||= [] | |
end | |
def human_test_names | |
@human_test_names ||= {} | |
end | |
# Creates a test method. | |
# | |
# @name [String] Your can use a human readable sentence to name your tests e.g. | |
# `test 'validation of Japanease VAT numbers` instead of having to write | |
# `def test_validation_of_japanease_vat_numbers` | |
# | |
# @options [Hash] Currently only :tdd option is supported. Imagine, your have | |
# created a test for the new, not yet existing functionality and would like | |
# to communicate your intentions to your coworker. So you want to commit the | |
# change. But you do not want to "break the build". And commenting out tests | |
# is really dirty - you want to track the progress, not sweep the dirt under | |
# the carpet. If you define your test like `test 'my test', :tdd => 'in_progress',` | |
# then the test is always executed and provides feedback but does not make | |
# the test suit fail. | |
# | |
# @block The body of your test. If you do not provide any, then test marked | |
# as pending. | |
def test(name, options={}, &block) | |
test_name = "test_#{name.gsub(' ','_')}" | |
raise ArgumentError, "#{test_name} is already defined" if | |
self.instance_methods.include? test_name.to_s | |
if block | |
define_method test_name, &block | |
if options[:tdd] # e.g. :tdd => 'in_progress' | |
ignore_failures << test_name | |
end | |
else | |
puts "PENDING: #{name}" | |
end | |
human_test_names[test_name] = name | |
end | |
# Defines test as pending. The test will NOT be executed. | |
# Use this instead of commenting out tests. | |
def xtest(name, options={}, &block) | |
puts "PENDING #{name}" | |
end | |
end | |
class Test::Unit::TestCase | |
# A hook. Override this method in your class to do something on every | |
# failure. E.g. for a web application you could store the content | |
# of the last response and show it in a browser. | |
# You can also augment the test log with additional data by returning | |
# additional message from your function. | |
# Example: | |
# def on_failure(e) | |
# if (last_response rescue false) | |
# if last_response.body.strip.empty? | |
# return ' [Empty response body]' | |
# else | |
# save_and_open_page if ENV['ASSERT_OPENS_PAGE'] and ENV['ASSERT_OPENS_PAGE']=='true' | |
# return '' | |
# end | |
# end | |
# end | |
def on_failure(e) | |
end | |
# Please set the VERBOSE_TEST environment variable to show single test steps | |
# somewhat similar to RSpec and stories. | |
def verbose_mode? | |
ENV['VERBOSE_TEST'] && ENV['VERBOSE_TEST'].downcase == 'true' | |
end | |
# Supports test driven development by enabling non-critical failures | |
# for features in_progress. No temporary uncommenting needed then. | |
# See also TestCase::test | |
def run(result) | |
method_name = name[/^[^(]+/] | |
name_to_show = self.class.human_test_names[method_name] || method_name | |
puts "\n\nTEST #{name_to_show } (#{self.class.name})\n" if verbose_mode? | |
yield(STARTED, name) | |
@_result = result | |
begin | |
setup | |
__send__(@method_name) | |
rescue Test::Unit::AssertionFailedError => e | |
msg = e.message | |
msg << on_failure(e).to_s | |
method_name = $1 if name =~ /(.*)\(/ | |
if method_name and self.class.ignore_failures and self.class.ignore_failures.include? method_name | |
puts "\nNon-critical " + | |
Test::Unit::Failure.new(name, filter_backtrace(e.backtrace), msg).long_display | |
puts '--' | |
else | |
add_failure(msg, e.backtrace) | |
end | |
rescue Exception | |
raise if PASSTHROUGH_EXCEPTIONS.include? $!.class | |
add_error($!) | |
ensure | |
begin | |
teardown | |
rescue Test::Unit::AssertionFailedError => e | |
add_failure(e.message, e.backtrace) | |
rescue Exception | |
raise if PASSTHROUGH_EXCEPTIONS.include? $!.class | |
add_error($!) | |
end | |
end | |
result.add_run | |
yield(FINISHED, name) | |
end | |
# See also `expect`. | |
def prepare(msg) | |
puts "Prepare #{msg}\n" if verbose_mode? | |
yield if block_given? | |
end | |
# Allows for structuring the implementation of your tests - offers an | |
# abstraction level between the test method and implemenatation statements. | |
# You can also use it to fold your code in the editor. | |
# Extends the message of the failed assertions. | |
# See also `prepare`. | |
def expect(msg) | |
begin | |
puts "Expect #{msg}\n" if verbose_mode? | |
yield if block_given? | |
rescue Test::Unit::AssertionFailedError => e | |
extended = Test::Unit::AssertionFailedError.new("#{msg}: #{e.message}") | |
extended.set_backtrace e.backtrace | |
raise extended | |
end | |
end | |
# Makes the difference between hashes easy to grasp for humans. | |
# Compare it to the original implementation, which just prints both hashes | |
# after each other. | |
def assert_hashes_equal(expected, actual, msg=nil) | |
expected_keys = expected.keys.sort | |
actual_keys = actual.keys.sort | |
if !(too_many = actual_keys - expected_keys).empty? | |
flunk "Too many keys in the actual Hash: #{too_many.inspect}. #{msg}" | |
end | |
if !(missing = actual_keys - expected_keys).empty? | |
flunk "Missing keys in the actual Hash: #{missing.inspect}. #{msg}" | |
end | |
expected.each do |key, value| | |
assert_equal value, actual[key], "key '#{key}' #{msg}" | |
end | |
assert_equal expected, actual, msg | |
end | |
# Makes the difference between arrays easy to grasp for humans. | |
# Compare it to the original implementation, which just prints both arrays | |
# after each other. | |
def assert_arrays_equal(expected, actual, msg=nil) | |
assert_equal expected.length, actual.length, "Arrays of same length expected. #{msg}" | |
expected.each_with_index do |value, i| | |
assert_equal value, actual[i], "Array element #{i}. #{msg}" | |
end | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment