For fun, I thought I would start a new Ruby on Rails project and use MiniTest instead of Test::Unit. Why? Well MiniTest is Ruby 1.9s testing framework dejour, and I suspect we will see more and more new projects adopt it. It has a built in mocking framework and RSpec like contextual syntax. You can probably get away with fewer gems in your Gemfile because of that.
Getting started is always the hardest part - let's jump in with a new rails project
rails new tddforme --skip-test-unit
Standard stuff. MiniTest sits nicely next to Test::Unit, so you can leave it in if you prefer. I've left it out just to keep things neat and tidy for now.
Now we update the old Gemfile:
group :development, :test do gem "minitest" end
and of course, bundle it all up.....from the command line:
$ bundle
Note that if you start experiencing strange errors when we get in to the generators later on, make sure you read about rails not finding a JavaScript runtime. Fire up your rails server and check everything is hunky dory if you fancy.
A Model
For the purposes of testing our test framework, we are going to put together a small model. Let's call it DevelopmentMethodology and give it a couple of fields - perhaps a name and a description.
$ rails g model DevelopmentMethodology name:string description:string
Set up the test structure
Now we have a model and a migration. I like to write my first tests now. Yes I know, crazy, before I've even migrated......anyway, because I created my project with no test-unit, I don't even have a test directory, so I'll create it now along with a few other subdirectories that will mimic the usual testing structure
$ mkdir test $ mkdir test/fixtures $ mkdir test/unit
Now, inside the test directory, I'll set up a basic test_helper file:
$ touch test/test_helper.rb
The code for my test_helper is almost boilerplate - I'm putting the Rails Environment into "test" and loading up the Rails and then simply including the minitest framework.
ENV["RAILS_ENV"] = "test" require File.expand_path('../../config/environment', __FILE__) require "minitest/autorun"
If you kept test-unit in your project, you'll have a test_helper already that will look similar to this, but won't yet bring in minitest. In this case you can choose to replace the "require" statement in that test_helper.rb file or you can create a new minitest_helper.rb file with the code.
Petit pause
Let's take a breath - go get a cup of coffee. We've covered a lot of ground. So far, the basic steps we have gone through are:
- Create a new Rails project, skipping the test-unit framework
- Update our Gemfile to include the minitest
- Bundle
- Create test directory structure
- Create test_helper.rb
Writing a test
At last, we can write our first test! Create a new file in the test/unit directory - call it whatever you want, I've chosen to name mine "development_methodology_test.rb" and a basic outline goes like this:
require 'test_helper' class TestDevelopmentMethodology < MiniTest::Unit::TestCase def test_our_test_framework_can_fail assert false end end
First, we include our test_helper.rb. Then we define a class to test our DevelopmentMethodology model - it inherits from on of the classes in MiniTest. Easy peasy. Then, we create a method called test_our_test_framework_can_fail in which we simply assert false. Why would I bother to do this? Well, this little piece of code allows me to verify that I've set up MiniTest correctly - I haven't yet bothered to write any tests against my own application yet. However, when I run the test, I get valuable output (assuming everything has been set up correctly) and I can see that MiniTest is running and is producing a failing test:
[]$ ruby -Itest test/unit=development_methodology_test.rb # Running tests: F Finished tests in 0.000601s, 1665.0543 tests/s, 1665.0543 assertions/s. 1) Failure: test_our_test_framework_can_fail(TestDevelopmentMethodology) [test/unit/development_methodology_test.rb:5]: Failed assertion, no message given. 1 tests, 1 assertions, 1 failures, 0 errors, 0 skips
I'll swiftly rewrite my test now that I know everything is working. I'm going to put a condition that a Development Methodology must contain a name.
require 'test_helper' class TestDevelopmentMethodology < MiniTest::Unit::TestCase def test_development_methodology_has_name development_methodology = DevelopmentMethodology.new assert !development_methodology.save end end
And run it
[]$ ruby -Itest test/unit=development_methodology_test.rb # Running tests: E Finished tests in 0.032036s, 31.2147 tests/s, 0.0000 assertions/s. 1) Error: test_development_methodology_has_name(TestDevelopmentMethodology): ActiveRecord::StatementInvalid: Could not find table 'development_methodologies'
It errors! But of course it does - we haven't run a migration yet. Let's do that and at the same time prepare our test database
$ rake db:migrate $ rake db:test:load
Run our test again and you should get a failing test
[]$ ruby -Itest test/unit=development_methodology_test.rb # Running tests: E Finished tests in 0.032036s, 31.2147 tests/s, 0.0000 assertions/s. 1) Failure: test_development_methodology_has_name(TestDevelopmentMethodology) [test/unit/development_methodology_test.rb:5]: Failed assertion, no message given. 1 tests, 1 assertions, 1 failures, 0 errors, 0 skips
Refactor our test
The first thing I'm going to do is refactor my test - because MiniTest includes both assert and refute statements, I think my test could be more readable if I write it like this (of course, running the test again produces a failed test as above):
require 'test_helper' class TestDevelopmentMethodology < MiniTest::Unit::TestCase def test_development_methodology_has_name development_methodology = DevelopmentMethodology.new refute development_methodology.save end end
Make the test pass
Making this test pass is simple - we just have to validate the presence of name. Edit app/models/development_methodology.rb and add in our validates
class DevelopmentMethodology < ActiveRecord::Base attr_accessible :description, :name validates_presence_of :name end
and now our test passes
$ ruby -Itest test/unit=development_methodology_test.rb Run options: --seed 20660 # Running tests: . Finished tests in 0.139823s, 7.1519 tests/s, 7.1519 assertions/s. 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
Make it purty
Now MiniTest comes with a lot built in - want your output to be in lovely colours - no problem - just run your tests like this and witness the beautiful and fabulous tests:
$ ruby -rminitest/pride -Itest test/unit=development_methodology_test.rb
Spec
A big plus point for MiniTest is the inclusion of spec by default. Watch what happens when we change our test to look like this:
require 'test_helper' describe DevelopmentMethodology do it "must have a name" do development_methodology = DevelopmentMethodology.new development_methodology.save.must_equal false end end
If you re-run your tests now, you get passing tests (obviously, because we've simply rewritten a passing test), but you've used some nice contextual RSpec like syntax all rolled up in MiniTest
That's good for today
I think that's enough of an introduction to using MiniTest with Rails. Hopefully, you've got some code to get you going so you can jump right in an explore MiniTest.
If you use require_relative '../test_helper' you can save the -Itest and do not need to add another load-path to your app.
ReplyDeleteAlso check out minitest-reporters, it has a nice red-green 'Default' reporter, so you no longer need to read the output, it's either red or green ;)
Good tip with require_relative.....Thanks.
ReplyDeleteI'll check out minitest-reporters
If you want a more immediate start to using MiniTest in a Rails app, check out minitest-rails.
ReplyDeletehttp://blowmage.com/2012/07/10/announcing-minitest-rails