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:

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, #exception_details, #flunk, #message, #mu_pp, #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:



864
865
866
867
868
# File 'lib/minitest/unit.rb', line 864

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

Instance Attribute Details

#__name__Object (readonly)

:nodoc:



822
823
824
# File 'lib/minitest/unit.rb', line 822

def __name__
  @__name__
end

Class Method Details

.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]


22
23
24
25
26
27
# File 'lib/minitest/benchmark.rb', line 22

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]


35
36
37
38
39
# File 'lib/minitest/benchmark.rb', line 35

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.



63
64
65
# File 'lib/minitest/benchmark.rb', line 63

def self.bench_range
  bench_exp 1, 10_000
end

.benchmark_methodsObject

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



45
46
47
# File 'lib/minitest/benchmark.rb', line 45

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.



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

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

.inherited(klass) ⇒ Object

:nodoc:



885
886
887
# File 'lib/minitest/unit.rb', line 885

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

.resetObject

:nodoc:



879
880
881
# File 'lib/minitest/unit.rb', line 879

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

.test_methodsObject

:nodoc:



902
903
904
905
906
907
908
909
910
911
912
913
914
# File 'lib/minitest/unit.rb', line 902

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

Defines test order and is subclassable. Defaults to :random but can be overridden to return :alpha if your tests are order dependent (read: weak).



894
895
896
# File 'lib/minitest/unit.rb', line 894

def self.test_order
  :random
end

.test_suitesObject

:nodoc:



898
899
900
# File 'lib/minitest/unit.rb', line 898

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


85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/minitest/benchmark.rb', line 85

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


129
130
131
132
133
134
135
136
137
# File 'lib/minitest/benchmark.rb', line 129

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


155
156
157
# File 'lib/minitest/benchmark.rb', line 155

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


175
176
177
# File 'lib/minitest/benchmark.rb', line 175

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


195
196
197
# File 'lib/minitest/benchmark.rb', line 195

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



204
205
206
207
208
209
210
# File 'lib/minitest/benchmark.rb', line 204

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



219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/minitest/benchmark.rb', line 219

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



241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/minitest/benchmark.rb', line 241

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



263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/minitest/benchmark.rb', line 263

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



870
871
872
873
# File 'lib/minitest/unit.rb', line 870

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

#io?Boolean

Returns:

  • (Boolean)


875
876
877
# File 'lib/minitest/unit.rb', line 875

def io?
  @__io__
end

#passed?Boolean

Returns true if the test passed.

Returns:

  • (Boolean)


919
920
921
# File 'lib/minitest/unit.rb', line 919

def passed?
  @passed
end

#run(runner) ⇒ Object

Runs the tests reporting the status to runner



832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
# File 'lib/minitest/unit.rb', line 832

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.__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.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

#setupObject

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



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

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


284
285
286
287
# File 'lib/minitest/benchmark.rb', line 284

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.



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

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.



293
294
295
296
297
298
299
# File 'lib/minitest/benchmark.rb', line 293

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