Minitest v1.0.0 on ruby 3.4
Published at 2025-11-08 14:27:41 -0800
The last post showed how minitest was originally written. That must have been back on ruby 1.7 or 1.8? If I were to write it today, it would probably look more like the code below.
Some things to pay attention to:
- 56 lines instead of 99!
- Cleaner test discovery. No more ObjectSpace.each_object!
- Better division of responsibilities. Look at autotest on both.
- Start with randomization and do it ACROSS all tests (not across classes, and then across their methods)
- A massive switch to endless methods.
- More attention paid to code formatting to help with visual pattern matching.
- Some tricks learned along the way to speed things up (eg using lambdas for the failure message to delay string evaluation).
This thing is a screamer. It is so fast compared to minitest 5.
I’m not fond of the wide lines. But I’ll take 160 lines with everything in a tight little pattern matchable group. Trade offs.
But is it especially ruby 3ish? Other than the endless methods, no not really. It is an interesting contrast but probably reflects more about me and how I think about code these days than about ruby.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
at_exit { exit Mini.autotest }
module Mini
def self.all_tests = Test.subklasses.flat_map { |k| k.tests.map { |m| [k, m] } }
def self.autotest = all_tests.shuffle.count { |k, m| k.run m }.zero?
class Test
@@subklasses = []
def self.subklasses = @@subklasses
def self.inherited(k) = subklasses << k
def self.tests = public_instance_methods(true).grep(/^test_/)
def self.run(m)
new.run m
nil
rescue Interrupt, NoMemoryError, SignalException, SystemExit
raise
rescue Exception => e
type = (Test::Assertion === e ? "Failure" : "Error")
puts "\n#{type}: #{klass}.#{meth}: #{e}"
puts e.backtrace
e
end
def run(m) = (setup; send m; teardown)
def setup = nil
def teardown = nil
def msg(m) = proc { m = m.call if m.respond_to?(:call); m.to_s.empty? or m = "#{m}.\n" ; "#{m}#{yield}" }
def assert(test, msg=nil) = (test or raise Assertion, msg(nil) { msg.call || "failed assertion (no message given)" }.call)
def assert_equal(exp, act, msg=nil) = assert exp == act, msg(msg) { "Expected %p to be equal to %p" % [act, exp] }
def assert_in_delta(exp, act, delta, msg=nil) = assert (exp.to_f - act.to_f).abs <= delta.to_f, msg(msg) { "Expected %s to be within %s of %s" % [exp, delta, act] }
def assert_instance_of(cls, obj, msg=nil) = assert cls === obj, msg(msg) { "Expected %p to be a %s" % [obj, cls] }
def assert_kind_of(cls, obj, msg=nil) = assert obj.kind_of?(cls), msg(msg) { "Expected %p to be a kind of %s" % [obj, cls] }
def assert_match(exp, act, msg=nil) = assert act =~ exp, msg(msg) { "Expected %p to match %p" % [act, exp]}
def assert_nil(obj, msg=nil) = assert obj.nil?, msg(msg) { "Expected %p to be nil" % [obj] }
def assert_operator(o1, op, o2, msg="") = assert o1.__send__(op, o2), msg(msg) { "Expected %s.%s(%s)) to be true" % [o1, op, o2] }
def assert_same(exp, act, msg=nil) = assert exp.equal?(act), msg(msg) { "Expected %p to be the same as %p" % [act, exp] }
def refute_equal(exp, act, msg=nil) = assert exp != act, msg(msg) { "Expected %p to not be equal to %p" % [act, exp] }
def refute_nil(obj, msg=nil) = assert ! obj.nil?, msg(msg) { "Expected %p to not be nil" % [obj] }
def refute_same(exp, act, msg=nil) = assert ! exp.equal?(act), msg(msg) { "Expected %p to not be the same as %p" % [act, exp] }
def assert_raises(exp, m=nil)
yield
assert false, "Expected %s to be raised" % [exp]
rescue Exception => e
assert exp === e, msg(m) { "Expected %s to be raised, but got %p" % [exp, e] }
e
end
Assertion = Class.new Exception
end # class Test
end # module Mini
|