Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A Guided Read of Minitest

A Guided Read of Minitest

Why is Minitest so much shorter than RSpec? What does Minitest's size tell us about it's philosophy on testing (and Ruby)?

Nate Berkopec

November 15, 2015
Tweet

More Decks by Nate Berkopec

Other Decks in Programming

Transcript

  1. → rspec-core: 6672 lines → rspec-expectations: 3672 lines → rspec-mocks:

    3930 lines → rspec-support: 1569 lines Total: 15843 Lines @nateberkopec
  2. "A stateist tester asserts that a method returns a particular

    value. A mockist tester asserts that a method triggers a specific set of interactions with the object's dependencies." - James Golick @nateberkopec
  3. $ ruby test.rb Run options: --seed 20624 # Running: .

    Finished in 0.001794s, 557.4841 runs/s, 557.4841 assertions/s. 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips @nateberkopec
  4. # minitest/autorun.rb require "minitest" # requires minitest/test require "minitest/spec" #

    requires minitest/expectations require "minitest/mock" Minitest.autorun @nateberkopec
  5. # minitest.rb module Minitest # ... snip ... def self.autorun

    at_exit { next if $! and not ($!.kind_of? SystemExit and $!.success?) exit_code = nil at_exit { @@after_run.reverse_each(&:call) exit exit_code || false } exit_code = Minitest.run ARGV } unless @@installed_at_exit @@installed_at_exit = true end end @nateberkopec
  6. def self.autorun if autorun_disabled? RSpec.deprecate("Requiring `rspec/autorun` when running RSpec via

    the `rspec` command") return elsif installed_at_exit? || running_in_drb? return end at_exit { perform_at_exit } @installed_at_exit = true end def self.perform_at_exit return unless $!.nil? || $!.is_a?(SystemExit) invoke end def self.invoke disable_autorun! status = run(ARGV, $stderr, $stdout).to_i exit(status) if status != 0 end def self.disable_autorun! @autorun_disabled = true end def self.installed_at_exit? @installed_at_exit ||= false end @nateberkopec
  7. # Minitest.autorun -> Minitest.run def self.run reporter, options = {}

    filter = options[:filter] || "/./" filter = Regexp.new $1 if filter =~ %r%/(.*)/% filtered_methods = self.runnable_methods.find_all { |m| filter === m || filter === "#{self}##{m}" } return if filtered_methods.empty? with_info_handler reporter do filtered_methods.each do |method_name| run_one_method self, method_name, reporter end end end @nateberkopec
  8. def self.run reporter, options = {} def self.run(reporter, options =

    {}) def some_method arg1 arg2 arg3 @nateberkopec
  9. # minitest.rb module Minitest class Runnable # Minitest::Test is a

    Runnable def self.runnables @@runnables end def self.inherited klass # :nodoc: self.runnables << klass super end end end @nateberkopec
  10. Stdlib used in Minitest → OptionParser → Thread → Mutex

    → StringIO → Tempfile @nateberkopec
  11. Core/stdlib classes RSpec overrides or re- implements → Set →

    flat_map → Thread local variables → rand (!!!) @nateberkopec
  12. # Minitest::Test # This was called in Minitest.run def self.runnable_methods

    methods = methods_matching(/^test_/) #one-liner that returns a list of methods as strings case self.test_order when :random, :parallel 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 @nateberkopec
  13. define_example_method :example define_example_method :it define_example_method :specify define_example_method :focus, :focus =>

    true define_example_method :fexample, :focus => true define_example_method :fit, :focus => true define_example_method :fspecify, :focus => true define_example_method :xexample, :skip => 'Temporarily skipped with xexample' define_example_method :xit, :skip => 'Temporarily skipped with xit' define_example_method :xspecify, :skip => 'Temporarily skipped with xspecify' define_example_method :skip, :skip => true define_example_method :pending, :pending => true @nateberkopec
  14. def self.define_example_method(name, extra_options={}) idempotently_define_singleton_method(name) do |*all_args, &block| desc, *args =

    *all_args options = Metadata.build_hash_from(args) options.update(:skip => RSpec::Core::Pending::NOT_YET_IMPLEMENTED) unless block options.update(extra_options) example = RSpec::Core::Example.new(self, desc, options, block) examples << example example end end @nateberkopec
  15. def assert_includes collection, obj, msg = nil msg = message(msg)

    { "Expected #{mu_pp(collection)} to include #{mu_pp(obj)}" } assert_respond_to collection, :include? assert collection.include?(obj), msg end @nateberkopec
  16. def assert test, msg = nil self.assertions += 1 unless

    test then msg ||= "Failed assertion, no message given." msg = msg.call if Proc === msg raise Minitest::Assertion, msg end true end @nateberkopec
  17. Other things that don't exist → stubs (singletons, Liskov Substitution

    (TestClass versions)) → assertnothingraised → a minitest runner @nateberkopec