Plugins - Active Test
Add to favoritesActiveTest
Contents
Modules and Classes
ActiveTest::Base - wraps Test::Unit::TestCase non-invasively, allows setup/teardown to be nested
ActiveTest::Subject - abstract class for test case types, like Controller and Model.
ActiveTest::Controller - subject for testing controllers, providing dynamic methods and asserts
ActiveTest::Model - subject for testing ActiveRecords, providing dynamic methods and asserts
ActiveTest::Asserts::Assigns - methods for asserting template variable assignments
ActiveTest::Asserts::Difference - methods for asserting difference and change for return values
ActiveTest::Asserts::Validations - methods for asserting ActiveRecord errors
Other
active_test.rb - load up ActiveTest
active_test/filter.rb - the one and only monkey patch: filter out anything in the ActiveTest module
Foreword
I would first like to precede anything with a quick reason why you should test. Testing is, contrary to popular belief, about design. It is not about stamping out bugs. Bugs are the result of bad designs or bad implementations of design. It is in the grey area between the two that makes testing seem like just a mosquito repellent. Tests, like the code they are ‘documenting’ or ‘specifying’ or ‘testing’, are just as prone to bad design or implementation. There are many theories swimming around since the inception of Test Driven Development that try to reconcile this problem of not seeing the wood for the trees.
Rather than come up with some grandiose redefinition of tests, let’s follow the path of least resistance. Why not just make them easier to write? That should help with most cases of bad design or implementation, because then it is faster to recognise a design issue.
About Similarities
While other implementations are attempting, with various success, to extend the functionality of Test::Unit and make it more Rails friendly, they have their own unique ways of setting up and developing tests. The price of their uniqueness is that they move away from the traditional thinking of a Rails developer. They introduce more complications of design and elements to be aware of, meaning more time to understand how something can be done. We should not have to think about the nuances of a test suite and model, how they relate or how to design our application within its specific language. The closer models and tests are to each other in design and usage, the easier it is to design them rapidly. That is why you will see a lot of similarities between ActiveTest and its paronymic relative, ActiveRecord.
There will, of course, be specific elements of ActiveTest you will have to learn if you want to use it, but that’s been kept as basic as possible.
Description
ActiveTest wraps Test::Unit in a Rails-like mould. It does a number of things which would be madness to write for a single project and has caused the author of this plugin considerable madness ‘for the fun of it’. Here are a few of its features:
- protects you from the disasters of monkey patching
- fixes issues such as setup/teardown nesting
- allows inheritance of test cases without hacking Test::Unit, et al.
- provides a highly extensible, flexible design — forget modifying Test::Unit::TestCase
- provides class-level macros, a la ActiveRecord, for standard test cases
- brings design (should do x for y because z) slightly closer to test cases
- provides instance-level assertions and helper methods
- provides default specifications for the major forms of testing in Rails
- follows Convention over Configuration, but allows both
- generally makes testing pleasurable
If you do nothing more than install ActiveTest and have all your tests inherit from ActiveTest::Base (not recommended, use one of the Subjects of ActiveTest::Subject itself), you will receive a few benefits without drawbacks:
- setup and teardown are nested
- all setups/teardowns are executed without super
- classes inheriting from ActiveTest::Base can be subclassed without detriment
- anything in the namespace of ActiveTest will not be run by Test::Unit
- in all other respects behaves exactly like Test::Unit::TestCase
The Design
At the heart of ActiveTest is ActiveTest::Base and the filter addition. The filter prevents anything within the namespace of ActiveTest to be run by Test::Unit::AutoRunner. The Base class itself makes setup and teardown nested, meaning any setup or teardown defined in classes inheriting from Base will be proc’d, stuffed in a stack and executed in the order of definition when setup or teardown are called. This allows all sorts of nifty things to happen seamlessly, such as inheritance.
Base is extended by ActiveTest::Subject, the abstract class for Subjects. Specific sets of ActiveTest::Asserts are mixed into each pre-defined Subject, giving a small set of assertions and convenience methods. Each Subject also has a number of ‘behaviours’ which they can test. These are the macros which can be called through dynamic class methods, such as +succeeds_on :index+.
There is clean distinction between instance and class methods. All class methods on a Subject are behaviours. All instance methods are assertions (e.g. assert_difference), actions (e.g. index_items or find_all), test cases or helping methods.
The class methods are your metalanguage. If you learn the few patterns for each Subject, test-driven design will become a breeze. Read on for more specifics.
Subjects
Subjects are the types of tests you will run, such as Controller or Model. Virgin Subjects can be created by extending the ActiveTest::Subject class itself, exactly how one creates new models inheriting from ActiveRecord::Base. You can, if you wish to have a completely blank slate, inherit directly from ActiveTest::Base too if you do not want behaviours, but you will most likely use the following formulations:
class ExampleControllerTest < ActiveTest::Controller end class ExampleTest < ActiveTest::Model end etc.
The best way to think of subjects is that they are providing models of the Rails domain and ways of specifying their functionality — you already do much of this in the code itself. Since everyone has a controller, view, helper, and record, we can make certain assumptions that, say, ActiveRecord cannot. This is not differing from the ActiveRecord metaphors — it is just filling in some blanks because we can assume a lot with relatively high accuracy. If you don’t like the convenience this provides, just extend ActiveTest::Subject. You’ll get all the fancy stuff of Test::Unit::TestCase and the ability to macro your tests with ‘define_behaviour’. If you don’t even want that, use ActiveTest::Base and you’ll get nothing but nested setup/teardown and the ability to inherit test cases.
Dynamic Methods: Behaviours and Actions
Dynamic methods are at the heart of ActiveTest’s prevention of bad design. They follow rules of simple English and help you think more about the specifications of your design than the programming of a test case. In most cases, it will be the behaviours of your application’s actions that will concern you. If you already think about the actions for each of your objects and how they behave, using Subject dynamic methods will become second nature.
A Subject’s behaviour class method tends to follow this formulation:
[behaviour] [action], [options]
From this syntax you can see that you test a given behaviour for an action in your application, with an optional indicator that it is an edge case (the options). It’s pretty straight forward.
The behaviour may be, for example, ‘succeeds_on’, ‘fails_on’, ‘assigns_records_on’ or ‘update_record_on’. A behaviour constrains the test case to a predefined set of assertions which will be run for the action you specify.
The action could be, for example, ‘index’ or ‘new’. Actions map directly to the actual methods on your controllers or models.
Assertions
ActiveTest allows you to create ‘assertion packages’. Think ‘Acts As …’. ActiveTest comes with a modest set of assertions. They are enumerated in the contents above. Each of them are pre-included in one or many Subjects for your immediate use. For example, all subjects have ActiveTest::Asserts::Difference, but only ActiveTest::Controller has ActiveTest::Asserts::Assigns. Becoming familiar with the assertions available to you can dramatically speed up your testing.
Inheritance Regained
For a long time, those using Test::Unit have not been able to effectively manipulate inheritance without hacking Test::Unit directly or working around a number of errors caused by inheriting from another test case. The two largest offenders which ActiveTest fixes are, as mentioned, setup/teardown and providing a namespace for ‘abstract tests’, such as the ActiveTest framework itself. We can now (again) do such glorious things as this:
class ActiveTest::BaseControllerTest < ActiveTest::Controller
setup
succeeds_on :index
succeeds_on :show
end
# This inherits setup, index_items and show_items
class ArticlesControllerTest < ActiveTest::BaseControllerTest
succeeds_on :new
succeeds_on :create
succeeds_on :edit
succeeds_on :update
end
Self-Documenting Code
There is a serious concern among developers that making tests more DRY will lose them their ability to document their application through TDD/BDD. As the tests become more terse (so the logic goes) there is less to explain the minutest behaviour of the application. For those who use the agiledox rake task or the equivalent which trawls through the source for standard test method patterns, there is one for ActiveTest too. Unlike its predecessor, it loads up all the tests and libraries and scans each class for the methods which have been created.
The rake task, activetest:agiledox, can be called from the root of your project directory in two ways:
$ rake activetest:agiledox
... checks everything in test/**/*_test.rb
$ rake activetest:agiledox -- test/unit/articles_test.rb test/unit/pages_test.rb
... checks only those files
Example Test Case (Including Commentary)
The following test case is a real example from ActiveTest’s self-tests.
class ArticlesControllerTest < ActiveTest::Controller
fixtures :articles
# Each Subject has a setup class method, some of which take options
setup
# Most dynamic methods default to a convention
succeeds_on :index
assigns_records_on :index
succeeds_on :new
assigns_records_on :new
# Options may be given.
succeeds_on :show, :parameters => { :id => 1 }
assigns_records_on :show, :parameters => { :id => 1 }
fails_on :show, :parameters => { :id => 19361 }
# Many options are flexible. Here, you can set parameters to a method or proc which is
# evaluated in the instance scope (Note: it is defined in the class scope).
succeeds_on :create, :parameters => :a_good_article
creates_record_on :create, :parameters => :a_good_article
fails_on :create, :parameters => proc { a_good_article[:article].merge(:body => nil) }
succeeds_on :edit, :parameters => proc {{ :id => articles(:nice_article).id }}
fails_on :edit, :parameters => { :id => 19361 }
# shortcutting a proc which is reused
@update_proc = proc {{ :id => articles(:nice_article).id, :article => { :body => "splat" } }}
succeeds_on :update, :parameters => @update_proc
updates_record_on :update, :parameters => @update_proc
fails_on :update, :parameters => { :id => 19361 }
record_unchanged_on :update, :parameters => { :id => 19361 }
succeeds_on :destroy, :parameters => proc {{ :id => articles(:nice_article).id }}
deletes_record_on :destroy, :parameters => proc {{ :id => articles(:nice_article).id }}
fails_on :destroy, { :id => 19361 }
succeeds_on :empty_collector
assigns_empty_on :empty_collector
protected
# return a hash for parameters
def a_good_article
{:article => { :title => "And now...", :body => "for something completely different" }}
end
end
Extending ActiveTest
Now that you’ve seen everything that ActiveTest is about, you’re probably thinking it isn’t enough. Well, half of this library is about making it easy to DRY up tests without losing the power to design and specify. In bringing the concept of modeling to tests, so too come the powers of extension.
Extending ActiveTest is as easy as subclassing and including. It uses the same design as extending ActiveRecord, meaning you can have plugins for custom ActiveTest classes, Subjects or Asserts with a minimum of fuss.
Extending ActiveTest::Base or ActiveTest::Subject
When you want to extend the behaviour of ActiveTest, work on Base or Subject. Base is only for situations which will not be directly inherited for a test case. This is just a matter of design; there is nothing restricting you from doing otherwise. However, any addition to the way tests behave beyond the setup/teardown nesting will most likely be added to Subject, so parallels to ActiveTest::Controller ought to inherit from Subject.
class ActiveTest::Library < ActiveTest::Base
end
class ActiveTest::YourSubject < ActiveTest::Subject
end
ActiveTest::YourSubject.class_eval do
# include the relevant assertions here
end
Extending ActiveTest::Asserts
If you want to create a new assertion suite for a particular Subject, all you need to do is create a plugin with this init.rb:
begin
require 'active_test'
ActiveTest::PickASubject.class_eval do
include ActiveTest::Asserts::YourAssertions
end
# add more relevant subjects here
rescue LoadError
puts "Please install ActiveTest to use this plugin"
end
And in your plugin/lib:
module ActiveTest
module Asserts
module YourAssertion
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
end
def instance_method
end
end
end
end
That all looks very familiar, doesn’t it?
http://www.mathewabonyi.com/articles/2006/08/14/activetest-rails-style-testing
http://mabs29.googlecode.com/svn/trunk/plugins/active_test
Rails' (MIT)
Testing
