Using code generation in tests
April 20th, 2006
Not sure if this falls into the useful or into the weird category, but I’m beginning to do code generation in my behavior specifications…
Take a look at the behavior specification:
1 2 3 4 5 6 7 8 9 10 11 12 |
require File.dirname(__FILE__) + '/../test_helper' class TransactionStateOrderingTest < Test::Unit::TestCase TransactionState::StatusNames.each do |status| define_method("test_#{status}_query_method") do assert TransactionState.send(status).send("#{status}?"), status (TransactionState::StatusNames - [status]).each do |inner_status| assert !TransactionState.send(status).send("#{inner_status}?"), inner_status end end end end |
And the corresponding implementation:
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 |
class TransactionState StatusNames = %w(started approved processed completed cancelled).freeze attr_reader :name, :index alias_method :to_s, :name def initialize(name) @name, @index = name.to_s, StatusNames.index(name.to_s) raise "Unknown status name: #{name.inspect}" unless @index end # Generate instance methods to query the state of this instance. # Generates #started? and #completed?, among others. StatusNames.each do |status| define_method("#{status}?") do self.name == TransactionState.send(status).name end end # Generate class methods to return pre-instantiated # TransactionState objects, one per status name. class << self @@states_cache = Hash.new StatusNames.each do |status| @@states_cache[status.to_sym] = TransactionState.new(status) define_method(status) do @@states_cache[status.to_sym] end end end Statuses = StatusNames.map {|status| TransactionState.send(status)} end |
Nothing too earth shattering, except for the specs. How do I ensure the specs were properly generated ? I didn’t, except for confirming that the number of tests and assertions went up as expected.