Class: MiniTest::Unit::TestCase

Inherits:
Object
  • Object
show all
Includes:
Assertions
Defined in:
lib/minitest/unit.rb,
lib/minitest/benchmark.rb

Overview

Subclass TestCase to create your own tests. Typically you'll want a TestCase subclass per implementation class.

See MiniTest::Assertions

Direct Known Subclasses

Spec

Constant Summary collapse

PASSTHROUGH_EXCEPTIONS =
[NoMemoryError, SignalException,
Interrupt, SystemExit]
SUPPORTS_INFO_SIGNAL =

:nodoc:

Signal.list['INFO']

Constants included from Assertions

Assertions::WINDOZE

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Assertions

#_assertions, #_assertions=, #assert, #assert_block, #assert_empty, #assert_equal, #assert_in_delta, #assert_in_epsilon, #assert_includes, #assert_instance_of, #assert_kind_of, #assert_match, #assert_nil, #assert_operator, #assert_output, #assert_raises, #assert_respond_to, #assert_same, #assert_send, #assert_silent, #assert_throws, #capture_io, diff, #diff, diff=, #exception_details, #flunk, #message, #mu_pp, #mu_pp_for_diff, #pass, #refute, #refute_empty, #refute_equal, #refute_in_delta, #refute_in_epsilon, #refute_includes, #refute_instance_of, #refute_kind_of, #refute_match, #refute_nil, #refute_operator, #refute_respond_to, #refute_same, #skip

Constructor Details

#initialize(name) ⇒ TestCase

:nodoc:


971
972
973
974
975
# File 'lib/minitest/unit.rb', line 971

def initialize name # :nodoc:
  @__name__ = name
  @__io__ = nil
  @passed = nil
end

Instance Attribute Details

#__name__Object (readonly)

:nodoc:


927
928
929
# File 'lib/minitest/unit.rb', line 927

def __name__
  @__name__
end

Class Method Details

.add_setup_hook(arg = nil, &block) ⇒ Object

Adds a block of code that will be executed before every TestCase is run. Equivalent to setup, but usable multiple times and without re-opening any classes.

All of the setup hooks will run in order after the setup method, if one is defined.

The argument can be any object that responds to #call or a block. That means that this call,

MiniTest::TestCase.add_setup_hook { puts "foo" }

... is equivalent to:

module MyTestSetup
  def call
    puts "foo"
  end
end

MiniTest::TestCase.add_setup_hook MyTestSetup

The blocks passed to add_setup_hook take an optional parameter that will be the TestCase instance that is executing the block.


1081
1082
1083
1084
# File 'lib/minitest/unit.rb', line 1081

def self.add_setup_hook arg=nil, &block
  hook = arg || block
  @setup_hooks << hook
end

.add_teardown_hook(arg = nil, &block) ⇒ Object

Adds a block of code that will be executed after every TestCase is run. Equivalent to teardown, but usable multiple times and without re-opening any classes.

All of the teardown hooks will run in reverse order after the teardown method, if one is defined.

The argument can be any object that responds to #call or a block. That means that this call,

MiniTest::TestCase.add_teardown_hook { puts "foo" }

... is equivalent to:

module MyTestTeardown
  def call
    puts "foo"
  end
end

MiniTest::TestCase.add_teardown_hook MyTestTeardown

The blocks passed to add_teardown_hook take an optional parameter that will be the TestCase instance that is executing the block.


1130
1131
1132
1133
# File 'lib/minitest/unit.rb', line 1130

def self.add_teardown_hook arg=nil, &block
  hook = arg || block
  @teardown_hooks << hook
end

.bench_exp(min, max, base = 10) ⇒ Object

Returns a set of ranges stepped exponentially from min to max by powers of base. Eg:

bench_exp(2, 16, 2) # => [2, 4, 8, 16]

28
29
30
31
32
33
# File 'lib/minitest/benchmark.rb', line 28

def self.bench_exp min, max, base = 10
  min = (Math.log10(min) / Math.log10(base)).to_i
  max = (Math.log10(max) / Math.log10(base)).to_i

  (min..max).map { |m| base ** m }.to_a
end

.bench_linear(min, max, step = 10) ⇒ Object

Returns a set of ranges stepped linearly from min to max by step. Eg:

bench_linear(20, 40, 10) # => [20, 30, 40]

41
42
43
44
45
# File 'lib/minitest/benchmark.rb', line 41

def self.bench_linear min, max, step = 10
  (min..max).step(step).to_a
rescue LocalJumpError # 1.8.6
  r = []; (min..max).step(step) { |n| r << n }; r
end

.bench_rangeObject

Specifies the ranges used for benchmarking for that class. Defaults to exponential growth from 1 to 10k by powers of 10. Override if you need different ranges for your benchmarks.

See also: ::bench_exp and ::bench_linear.


69
70
71
# File 'lib/minitest/benchmark.rb', line 69

def self.bench_range
  bench_exp 1, 10_000
end

.benchmark_methodsObject

Returns the benchmark methods (methods that start with bench_) for that class.


51
52
53
# File 'lib/minitest/benchmark.rb', line 51

def self.benchmark_methods # :nodoc:
  public_instance_methods(true).grep(/^bench_/).map { |m| m.to_s }.sort
end

.benchmark_suitesObject

Returns all test suites that have benchmark methods.


58
59
60
# File 'lib/minitest/benchmark.rb', line 58

def self.benchmark_suites
  TestCase.test_suites.reject { |s| s.benchmark_methods.empty? }
end

.i_suck_and_my_tests_are_order_dependent!Object

Call this at the top of your tests when you absolutely positively need to have ordered tests. In doing so, you're admitting that you suck and your tests are weak.


997
998
999
1000
1001
# File 'lib/minitest/unit.rb', line 997

def self.i_suck_and_my_tests_are_order_dependent!
  class << self
    define_method :test_order do :alpha end
  end
end

.inherited(klass) ⇒ Object

:nodoc:


1003
1004
1005
1006
1007
# File 'lib/minitest/unit.rb', line 1003

def self.inherited klass # :nodoc:
  @@test_suites[klass] = true
  klass.reset_setup_teardown_hooks
  super
end

.resetObject

:nodoc:


986
987
988
# File 'lib/minitest/unit.rb', line 986

def self.reset # :nodoc:
  @@test_suites = {}
end

.reset_setup_teardown_hooksObject

:nodoc:


1048
1049
1050
1051
# File 'lib/minitest/unit.rb', line 1048

def self.reset_setup_teardown_hooks # :nodoc:
  @setup_hooks = []
  @teardown_hooks = []
end

.setup_hooksObject

:nodoc:


1086
1087
1088
1089
1090
1091
1092
# File 'lib/minitest/unit.rb', line 1086

def self.setup_hooks # :nodoc:
  if superclass.respond_to? :setup_hooks then
    superclass.setup_hooks
  else
    []
  end + @setup_hooks
end

.teardown_hooksObject

:nodoc:


1135
1136
1137
1138
1139
1140
1141
# File 'lib/minitest/unit.rb', line 1135

def self.teardown_hooks # :nodoc:
  if superclass.respond_to? :teardown_hooks then
    superclass.teardown_hooks
  else
    []
  end + @teardown_hooks
end

.test_methodsObject

:nodoc:


1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
# File 'lib/minitest/unit.rb', line 1017

def self.test_methods # :nodoc:
  methods = public_instance_methods(true).grep(/^test/).map { |m| m.to_s }

  case self.test_order
  when :random then
    max = methods.size
    methods.sort.sort_by { rand max }
  when :alpha, :sorted then
    methods.sort
  else
    raise "Unknown test_order: #{self.test_order.inspect}"
  end
end

.test_orderObject

:nodoc:


1009
1010
1011
# File 'lib/minitest/unit.rb', line 1009

def self.test_order # :nodoc:
  :random
end

.test_suitesObject

:nodoc:


1013
1014
1015
# File 'lib/minitest/unit.rb', line 1013

def self.test_suites # :nodoc:
  @@test_suites.keys.sort_by { |ts| ts.name.to_s }
end

Instance Method Details

#assert_performance(validation, &work) ⇒ Object

Runs the given work, gathering the times of each run. Range and times are then passed to a given validation proc. Outputs the benchmark name and times in tab-separated format, making it easy to paste into a spreadsheet for graphing or further analysis.

Ranges are specified by ::bench_range.

Eg:

def bench_algorithm
  validation = proc { |x, y| ... }
  assert_performance validation do |x|
    @obj.algorithm
  end
end

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/minitest/benchmark.rb', line 91

def assert_performance validation, &work
  range = self.class.bench_range

  io.print "#{__name__}"

  times = []

  range.each do |x|
    GC.start
    t0 = Time.now
    instance_exec(x, &work)
    t = Time.now - t0

    io.print "\t%9.6f" % t
    times << t
  end
  io.puts

  validation[range, times]
end

#assert_performance_constant(threshold = 0.99, &work) ⇒ Object

Runs the given work and asserts that the times gathered fit to match a constant rate (eg, linear slope == 0) within a given threshold. Note: because we're testing for a slope of 0, R^2 is not a good determining factor for the fit, so the threshold is applied against the slope itself. As such, you probably want to tighten it from the default.

See www.graphpad.com/curvefit/goodness_of_fit.htm for more details.

Fit is calculated by #fit_linear.

Ranges are specified by ::bench_range.

Eg:

def bench_algorithm
  assert_performance_constant 0.9999 do |x|
    @obj.algorithm
  end
end

135
136
137
138
139
140
141
142
143
# File 'lib/minitest/benchmark.rb', line 135

def assert_performance_constant threshold = 0.99, &work
  validation = proc do |range, times|
    a, b, rr = fit_linear range, times
    assert_in_delta 0, b, 1 - threshold
    [a, b, rr]
  end

  assert_performance validation, &work
end

#assert_performance_exponential(threshold = 0.99, &work) ⇒ Object

Runs the given work and asserts that the times gathered fit to match a exponential curve within a given error threshold.

Fit is calculated by #fit_exponential.

Ranges are specified by ::bench_range.

Eg:

def bench_algorithm
  assert_performance_exponential 0.9999 do |x|
    @obj.algorithm
  end
end

161
162
163
# File 'lib/minitest/benchmark.rb', line 161

def assert_performance_exponential threshold = 0.99, &work
  assert_performance validation_for_fit(:exponential, threshold), &work
end

#assert_performance_linear(threshold = 0.99, &work) ⇒ Object

Runs the given work and asserts that the times gathered fit to match a straight line within a given error threshold.

Fit is calculated by #fit_linear.

Ranges are specified by ::bench_range.

Eg:

def bench_algorithm
  assert_performance_linear 0.9999 do |x|
    @obj.algorithm
  end
end

181
182
183
# File 'lib/minitest/benchmark.rb', line 181

def assert_performance_linear threshold = 0.99, &work
  assert_performance validation_for_fit(:linear, threshold), &work
end

#assert_performance_power(threshold = 0.99, &work) ⇒ Object

Runs the given work and asserts that the times gathered curve fit to match a power curve within a given error threshold.

Fit is calculated by #fit_power.

Ranges are specified by ::bench_range.

Eg:

def bench_algorithm
  assert_performance_power 0.9999 do |x|
    @obj.algorithm
  end
end

201
202
203
# File 'lib/minitest/benchmark.rb', line 201

def assert_performance_power threshold = 0.99, &work
  assert_performance validation_for_fit(:power, threshold), &work
end

#fit_error(xys) ⇒ Object

Takes an array of x/y pairs and calculates the general R^2 value.

See: en.wikipedia.org/wiki/Coefficient_of_determination


210
211
212
213
214
215
216
# File 'lib/minitest/benchmark.rb', line 210

def fit_error xys
  y_bar  = sigma(xys) { |x, y| y } / xys.size.to_f
  ss_tot = sigma(xys) { |x, y| (y    - y_bar) ** 2 }
  ss_err = sigma(xys) { |x, y| (yield(x) - y) ** 2 }

  1 - (ss_err / ss_tot)
end

#fit_exponential(xs, ys) ⇒ Object

To fit a functional form: y = ae^(bx).

Takes x and y values and returns [a, b, r^2].

See: mathworld.wolfram.com/LeastSquaresFittingExponential.html


225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/minitest/benchmark.rb', line 225

def fit_exponential xs, ys
  n     = xs.size
  xys   = xs.zip(ys)
  sxlny = sigma(xys) { |x,y| x * Math.log(y) }
  slny  = sigma(xys) { |x,y| Math.log(y)     }
  sx2   = sigma(xys) { |x,y| x * x           }
  sx    = sigma xs

  c = n * sx2 - sx ** 2
  a = (slny * sx2 - sx * sxlny) / c
  b = ( n * sxlny - sx * slny ) / c

  return Math.exp(a), b, fit_error(xys) { |x| Math.exp(a + b * x) }
end

#fit_linear(xs, ys) ⇒ Object

Fits the functional form: a + bx.

Takes x and y values and returns [a, b, r^2].

See: mathworld.wolfram.com/LeastSquaresFitting.html


247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/minitest/benchmark.rb', line 247

def fit_linear xs, ys
  n   = xs.size
  xys = xs.zip(ys)
  sx  = sigma xs
  sy  = sigma ys
  sx2 = sigma(xs)  { |x|   x ** 2 }
  sxy = sigma(xys) { |x,y| x * y  }

  c = n * sx2 - sx**2
  a = (sy * sx2 - sx * sxy) / c
  b = ( n * sxy - sx * sy ) / c

  return a, b, fit_error(xys) { |x| a + b * x }
end

#fit_power(xs, ys) ⇒ Object

To fit a functional form: y = ax^b.

Takes x and y values and returns [a, b, r^2].

See: mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html


269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/minitest/benchmark.rb', line 269

def fit_power xs, ys
  n       = xs.size
  xys     = xs.zip(ys)
  slnxlny = sigma(xys) { |x, y| Math.log(x) * Math.log(y) }
  slnx    = sigma(xs)  { |x   | Math.log(x)               }
  slny    = sigma(ys)  { |   y| Math.log(y)               }
  slnx2   = sigma(xs)  { |x   | Math.log(x) ** 2          }

  b = (n * slnxlny - slnx * slny) / (n * slnx2 - slnx ** 2);
  a = (slny - b * slnx) / n

  return Math.exp(a), b, fit_error(xys) { |x| (Math.exp(a) * (x ** b)) }
end

#ioObject


977
978
979
980
# File 'lib/minitest/unit.rb', line 977

def io
  @__io__ = true
  MiniTest::Unit.output
end

#io?Boolean


982
983
984
# File 'lib/minitest/unit.rb', line 982

def io?
  @__io__
end

#passed?Boolean

Returns true if the test passed.


1034
1035
1036
# File 'lib/minitest/unit.rb', line 1034

def passed?
  @passed
end

#run(runner) ⇒ Object

Runs the tests reporting the status to runner


937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
# File 'lib/minitest/unit.rb', line 937

def run runner
  trap "INFO" do
    time = runner.start_time ? Time.now - runner.start_time : 0
    warn "%s#%s %.2fs" % [self.class, self.__name__, time]
    runner.status $stderr
  end if SUPPORTS_INFO_SIGNAL

  result = ""
  begin
    @passed = nil
    self.setup
    self.run_setup_hooks
    self.__send__ self.__name__
    result = "." unless io?
    @passed = true
  rescue *PASSTHROUGH_EXCEPTIONS
    raise
  rescue Exception => e
    @passed = false
    result = runner.puke self.class, self.__name__, e
  ensure
    begin
      self.run_teardown_hooks
      self.teardown
    rescue *PASSTHROUGH_EXCEPTIONS
      raise
    rescue Exception => e
      result = runner.puke self.class, self.__name__, e
    end
    trap 'INFO', 'DEFAULT' if SUPPORTS_INFO_SIGNAL
  end
  result
end

#run_setup_hooksObject

:nodoc:


1094
1095
1096
1097
1098
1099
1100
1101
1102
# File 'lib/minitest/unit.rb', line 1094

def run_setup_hooks # :nodoc:
  self.class.setup_hooks.each do |hook|
    if hook.respond_to?(:arity) && hook.arity == 1
      hook.call(self)
    else
      hook.call
    end
  end
end

#run_teardown_hooksObject

:nodoc:


1143
1144
1145
1146
1147
1148
1149
1150
1151
# File 'lib/minitest/unit.rb', line 1143

def run_teardown_hooks # :nodoc:
  self.class.teardown_hooks.reverse.each do |hook|
    if hook.respond_to?(:arity) && hook.arity == 1
      hook.call(self)
    else
      hook.call
    end
  end
end

#setupObject

Runs before every test. Use this to refactor test initialization.


1041
# File 'lib/minitest/unit.rb', line 1041

def setup; end

#sigma(enum, &block) ⇒ Object

Enumerates over enum mapping block if given, returning the sum of the result. Eg:

sigma([1, 2, 3])                # => 1 + 2 + 3 => 7
sigma([1, 2, 3]) { |n| n ** 2 } # => 1 + 4 + 9 => 14

290
291
292
293
# File 'lib/minitest/benchmark.rb', line 290

def sigma enum, &block
  enum = enum.map(&block) if block
  enum.inject { |sum, n| sum + n }
end

#teardownObject

Runs after every test. Use this to refactor test cleanup.


1046
# File 'lib/minitest/unit.rb', line 1046

def teardown; end

#validation_for_fit(msg, threshold) ⇒ Object

Returns a proc that calls the specified fit method and asserts that the error is within a tolerable threshold.


299
300
301
302
303
304
305
# File 'lib/minitest/benchmark.rb', line 299

def validation_for_fit msg, threshold
  proc do |range, times|
    a, b, rr = send "fit_#{msg}", range, times
    assert_operator rr, :>=, threshold
    [a, b, rr]
  end
end