software projects by ryan davis

minitest

Testing for people who value their time

Description

minitest provides a complete suite of testing facilities supporting TDD, BDD, mocking, and benchmarking.

“I had a class with Jim Weirich on testing last week and we were allowed to choose our testing frameworks. Kirk Haines and I were paired up and we cracked open the code for a few test frameworks…

I MUST say that minitest is very readable / understandable compared to the ‘other two’ options we looked at. Nicely done and thank you for helping us keep our mental sanity.”

– Wayne E. Seguin

minitest/unit is a small and incredibly fast unit testing framework. It provides a rich set of assertions to make your tests clean and readable.

minitest/spec is a functionally complete spec engine. It hooks onto minitest/unit and seamlessly bridges test assertions over to spec expectations.

minitest/benchmark is an awesome way to assert the performance of your algorithms in a repeatable manner. Now you can assert that your newb co-worker doesn’t replace your linear algorithm with an exponential one!

minitest/mock by Steven Baker, is a beautifully tiny mock object framework.

minitest/pride shows pride in testing and adds coloring to your test output. I guess it is an example of how to write IO pipes too. :P

minitest/unit is meant to have a clean implementation for language implementors that need a minimal set of methods to bootstrap a working test suite. For example, there is no magic involved for test-case discovery.

“Again, I can’t praise enough the idea of a testing/specing framework that I can actually read in full in one sitting!”

– Piotr Szotkowski

Features & Problems

Synopsis

Given that you’d like to test the following class:

1
2
3
4
5
6
7
8
9
class Meme
  def i_can_has_cheezburger?
    "OHAI!"
  end

  def will_it_blend?
    "YES!"
  end
end

Unit tests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require 'minitest/autorun'

class TestMeme < MiniTest::Unit::TestCase
  def setup
    @meme = Meme.new
  end

  def test_that_kitty_can_eat
    assert_equal "OHAI!", @meme.i_can_has_cheezburger?
  end

  def test_that_it_will_not_blend
    refute_match /^no/i, @meme.will_it_blend?
  end
end

Specs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
require 'minitest/autorun'

describe Meme do
  before do
    @meme = Meme.new
  end

  describe "when asked about cheeseburgers" do
    it "must respond positively" do
      @meme.i_can_has_cheezburger?.must_equal "OHAI!"
    end
  end

  describe "when asked about blending possibilities" do
    it "won't say no" do
      @meme.will_it_blend?.wont_match /^no/i
    end
  end
end

For rspec-like matcher support check out:

https://github.com/zenspider/minitest-matchers

Benchmarks

Add benchmarks to your regular unit tests. If the unit tests fail, the benchmarks won’t run.

1
2
3
4
5
6
7
8
9
10
11
# optionally run benchmarks, good for CI-only work!
require 'minitest/benchmark' if ENV["BENCH"]

class TestMeme < MiniTest::Unit::TestCase
  # Override self.bench_range or default range is [1, 10, 100, 1_000, 10_000]
  def bench_my_algorithm
    assert_performance_linear 0.9999 do |n| # n is a range value
      @obj.my_algorithm(n)
    end
  end
end

Or add them to your specs. If you make benchmarks optional, you’ll need to wrap your benchmarks in a conditional since the methods won’t be defined.

1
2
3
4
5
6
7
8
9
describe Meme do
  if ENV["BENCH"] then
    bench_performance_linear "my_algorithm", 0.9999 do |n|
      100.times do
        @obj.my_algorithm(n)
      end
    end
  end
end

outputs something like:

# Running benchmarks:

TestBlah	100	1000	10000
bench_my_algorithm	 0.006167	 0.079279	 0.786993
bench_other_algorithm	 0.061679	 0.792797	 7.869932

Output is tab-delimited to make it easy to paste into a spreadsheet. How awesome is that?

Mocks

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
class MemeAsker
  def initialize(meme)
    @meme = meme
  end

  def ask(question)
    method = question.tr(" ","_") + "?"
    @meme.__send__(method)
  end
end

require 'minitest/autorun'

describe MemeAsker do
  before do
    @meme = MiniTest::Mock.new
    @meme_asker = MemeAsker.new @meme
  end

  describe "#ask" do
    describe "when passed an unpunctuated question" do
      it "should invoke the appropriate predicate method on the meme" do
        @meme.expect :will_it_blend?, :return_value
        @meme_asker.ask "will it blend"
        @meme.verify
      end
    end
  end
end

Customizable Test Runner Types:

MiniTest::Unit.runner=(runner) provides an easy way of creating custom test runners for specialized needs. Justin Weiss provides the following real-world example to create an alternative to regular fixture loading:

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
class MiniTestWithHooks::Unit < MiniTest::Unit
  def before_suites
  end

  def after_suites
  end

  def _run_suites(suites, type)
    begin
      before_suites
      super(suites, type)
    ensure
      after_suites
    end
  end

  def _run_suite(suite, type)
    begin
      suite.before_suite
      super(suite, type)
    ensure
      suite.after_suite
    end
  end
end

module MiniTestWithTransactions
  class Unit < MiniTestWithHooks::Unit
    include TestSetupHelper

    def before_suites
      super
      setup_nested_transactions
      # load any data we want available for all tests
    end

    def after_suites
      teardown_nested_transactions
      super
    end
  end
end

MiniTest::Unit.runner = MiniTestWithTransactions::Unit.new

Get The Code

If you just want to use minitest, you can install it via RubyGems:
gem install minitest
Fork me on GitHub If you want to hack on minitest, clone it from GitHub:
git clone git://github.com/seattlerb/minitest

Latest Activity