Using code generation in tests
2006-04-20
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:
text/unit/transaction_state_test.rb
1 require File.dirname(FILE) + ‘/../test_helper’
2
3 class TransactionStateOrderingTest < Test::Unit::TestCase
4 TransactionState::StatusNames.each do |status|
5 define_method("test_#{status}query_method") do
6 assert TransactionState.send(status).send("#{status}?"), status
7 (TransactionState::StatusNames – [status]).each do |inner_status|
8 assert !TransactionState.send(status).send("#{inner_status}?"), innerstatus
9 end
10 end
11 end
12 end
And the corresponding implementation:
app/models/transaction_state.rb
1 class TransactionState
2 StatusNames = %w(started approved processed completed cancelled).freeze
3
4 attr_reader :name, :index
5 alias_method :to_s, :name
6
7 def initialize(name)
8name</span>, <span class="iv">index = name.to_s, StatusNames.index(name.to_s)
9 raise "Unknown status name: #{name.inspect}" unlessindex</span> <span class="no"><strong>10</strong></span> <span class="r">end</span> <span class="no">11</span> <span class="no">12</span> <span class="c"># Generate instance methods to query the state of this instance.</span> <span class="no">13</span> <span class="c"># Generates #started? and #completed?, among others.</span> <span class="no">14</span> <span class="co">StatusNames</span>.each <span class="r">do</span> |status| <span class="no"><strong>15</strong></span> define_method(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>status<span class="idl">}</span></span><span class="k">?</span><span class="dl">"</span></span>) <span class="r">do</span> <span class="no">16</span> <span class="pc">self</span>.name == <span class="co">TransactionState</span>.send(status).name <span class="no">17</span> <span class="r">end</span> <span class="no">18</span> <span class="r">end</span> <span class="no">19</span> <span class="no"><strong>20</strong></span> <span class="c"># Generate class methods to return pre-instantiated</span> <span class="no">21</span> <span class="c"># TransactionState objects, one per status name.</span> <span class="no">22</span> <span class="r">class</span> << <span class="cl">self</span> <span class="no">23</span> <span class="cv">states_cache</span> = <span class="co">Hash</span>.new <span class="no">24</span> <span class="co">StatusNames</span>.each <span class="r">do</span> |status| <span class="no"><strong>25</strong></span> <span class="cv">states_cache</span>[status.to_sym] = <span class="co">TransactionState</span>.new(status) <span class="no">26</span> define_method(status) <span class="r">do</span> <span class="no">27</span> <span class="cv">@states_cache[status.to_sym]
28 end
29 end
30 end
31
32 Statuses = StatusNames.map {|status| TransactionState.send(status)}
33 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.