Fixture Replacement Plugins

2,262 views

Published on

Published in: Technology, Business
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
2,262
On SlideShare
0
From Embeds
0
Number of Embeds
18
Actions
Shares
0
Downloads
6
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

Fixture Replacement Plugins

  1. 1. Fixture replacement plugins Jakub Suder Lunar Logic Polska
  2. 2. Why fixtures aren't enough „Rails test fixtures are evil and a plague upon developers.” http://b.logi.cx/2007/11/26/object-daddy „If you are still using fixtures, there is no hope for you. Become a forest ranger.” http://blog.adsdevshop.com/2009/02/23/factories-for-test-objects-use-them/
  3. 3. Why fixtures aren't enough <ul><li>not readable – hard to remember which record has what properties
  4. 4. no one remembers the „story” </li></ul>it &quot;should be possible to delete a folder only in a group that you're a member of&quot; do folder = folders(:ruby) folder.should be_deletable_by(users(:joe)) folder.should be_deletable_by(users(:joan)) folder.should_not be_deletable_by(users(:bill)) end
  5. 5. Why fixtures aren't enough <ul><li>information scattered over many files
  6. 6. fixtures can be invalid
  7. 7. some things are much more difficult than they should be
  8. 8. not DRY
  9. 9. slow? </li></ul>
  10. 10. Why fixtures aren't enough <ul><li>it can get complicated... </li></ul>class ScheduleEntryFfsBillingTest < Test::Unit::TestCase fixtures :parties, :locations, :cost_centers, :service_places, :activities, :logins, :tuple_domains, :tuples, :client_payer_relations, :credentials, :gl_mappings, :panels, :panel_members, :panel_payers, :fee_matrices, :fl_matrices, :payer_fee_matrices, :pfl_matrices, :pdcrf_matrices, :care_domains, :care_domains_objectives, :client_domains, :allowed_cost_centers, :authorizations, :positions, :accountabilities, :commissioners, :responsibles, :accountability_positions, :chart_entries, :observations, :gl_mappings, :gl_mapping_types, :tags, :taggings, :form_sets, :form_items, :choices def test_validate_ffs_billing count = ScheduleEntry.ffs_ready_to_bill.size assert_difference BillableItem, :count, 98 do entries = ScheduleEntry.validate_ffs_billing assert entries end assert_equal count-130, ScheduleEntry.ffs_ready_to_bill.size end end ( http://b.logi.cx/2007/11/26/object-daddy )
  11. 11. Solutions <ul><li>object generator? </li></ul>def create_event(options = {}) Event.create({:name => 'Event 1', :location => 'Krakow', :date => Date.today}).merge(options) end <ul><li>too simple, difficult to extend </li><ul><li>d oesn't handle associations well
  12. 12. doesn't handle uniqueness </li></ul></ul>
  13. 13. Solutions <ul><li>FixtureReplacement
  14. 14. Object Daddy
  15. 15. Factory Girl
  16. 16. Machinist
  17. 17. DM-Sweatshop </li></ul>
  18. 18. FixtureReplacement <ul><li>http://replacefixtures.rubyforge.org/ </li></ul>
  19. 19. FixtureReplacement (db/example_data.rb) module FixtureReplacement attributes_for :user do |u| password = String.random u.value = &quot;a value&quot;, u.other = &quot;other value&quot;, u.another = String.random, # random string 10 characters long u.one_more = String.random(15), # 15 characters long u.password = password, u.password_confirmation = password, u.associated_object = default_bar # expects attributes_for :bar end end
  20. 20. FixtureReplacement <ul><li>String.random
  21. 21. new_user -> User.new
  22. 22. create_user -> User.create!
  23. 23. default_user -> for use inside model_attributes definitions
  24. 24. new_user(:thing => &quot;overridden&quot;)
  25. 25. john = create_user(:username => &quot;john&quot;)
  26. 26. circumvents attr_protected by assigning manually </li></ul>
  27. 27. Object Daddy <ul><li>http://github.com/flogic/object_daddy/ </li></ul>
  28. 28. Object Daddy class User < ActiveRecord::Base generator_for :email, :start => 'test@domain.com' do |prev| user, domain = prev.split('@') user.succ + '@' + domain end generator_for :username, :method => :next_user generator_for :name, :start => 'Joe' generator_for(:start_time) { Time.now } generator_for :name, 'Joe' generator_for :age => 25 def self.next_user @last_username ||= 'testuser' @last_username.succ end end
  29. 29. Object Daddy class User < ActiveRecord::Base generator_for :ssn, :class => SSNGenerator end class SSNGenerator def self.next @last ||= '000-00-0000' @last = (&quot;%09d&quot; % (@last.gsub('-', '').to_i + 1)).sub(/^(d{3})(d{2})(d{4})$/, '1-2-3') end end
  30. 30. Object Daddy it &quot;should have a comment for every forum the user posts to&quot; do @user = User.generate @post = Post.generate @post.comments << Comment.generate(:title => 'first post!!11') @user.should have(1).comments end admin_user = User.generate! do |user| user.activate! user.add_role(&quot;admin&quot;) end User.spawn # User.new
  31. 31. Object Daddy <ul><li>/spec/exemplars/user_exemplar.rb
  32. 32. automatically creates objects for required relationships
  33. 33. relationships are always saved, regardless if you use spawn or generate </li></ul>
  34. 34. Factory Girl http://blog.adsdevshop.com/2009/02/23/factories-for-test-objects-use-them/ http://giantrobots.thoughtbot.com/2008/6/6/waiting-for-a-factory-girl http://www.thoughtbot.com/projects/factory_girl http://dev.thoughtbot.com/factory_girl/ http://github.com/thoughtbot/factory_girl/
  35. 35. Factory Girl Factory.define :user do |f| f.first_name 'John' f.last_name 'Smith' f.admin false f.email { Factory.next(:email) } f.email { |u| &quot;#{u.first_name}.#{u.last_name}@example.com&quot; } end Factory.sequence :email do |n| &quot;somebody#{n}@example.com&quot; end f.sequence(:email) { |n| &quot;person#{n}@example.com&quot; }
  36. 36. Factory Girl Factory.define :admin_user, :class => User do |f| f.first_name 'Bill' f.last_name 'Adminsky' f.email { Factory.next(:email) } f.admin true end Factory.define :admin, :parent => :user do |f| f.admin true end Factory.define :post do |f| f.title 'help!' f.approved true f.author { |a| a.association(:user [, :first_name => ...]) } # or: f.association :author [, :factory => :user] end
  37. 37. Factory Girl should &quot;only find approved posts&quot; do Factory(:post, :approved => false) Factory(:post, :approved => true) posts = Post.approved assert posts.all? { |p| p.approved? } end @post = Factory.build(:post, :title => '') # Post.new user_attributes = Factory.attributes_for(:user) # hash
  38. 38. Factory Girl <ul><li>test/factories.rb, spec/factories.rb
  39. 39. test/factories/*.rb, spec/factories/*.rb
  40. 40. saved associations for saved records, and unsaved associations for unsaved records
  41. 41. circumvents attr_protected </li></ul>
  42. 42. Factory Girl <ul><li>Factory Girl as Object Daddy :) </li></ul>require 'factory_girl/syntax/generate' Factory.define :user do |factory| factory.name 'John Smith' factory.email 'john.smith@gmail.com' end User.generate(:name => 'Johnny') User.generate! User.spawn
  43. 43. Machinist <ul><li>http://advent2008.hackruby.com/past/2008/12/05/machinist_for_your_test_data_factory _
  44. 44. http://toolmantim.com/articles/fixtureless_datas_with_machinist_and_sham
  45. 45. http://github.com/hassox/machinist/tree/master </li></ul>
  46. 46. Machinist # /spec/blueprints.rb, /test/blueprints.rb Business.blueprint do name { &quot;My Company&quot; } address { &quot;New York&quot; } web { &quot;http://www.example.com&quot; } email { &quot;info@example.com&quot; } end business = Business.make invalid_business = Business.make(:email => &quot;bad@email&quot;) Business.make_unsaved Business.plan # hash
  47. 47. Machinist User.blueprint do email { Sham.email } # or just: email end Sham.email { something random } Sham.invoice_no { |index| &quot;20080101-#{index}&quot; } Sham.name { Faker::Name.name } # http://faker.rubyforge.org/ Faker::Company.name Faker::Internet.email Faker::Internet.domain_name Faker::Lorem.sentence
  48. 48. Machinist User.blueprint do name { Sham.name } email { Sham.email } business { Business.make(:name => &quot;#{name} Shop&quot;) } # or just: business end
  49. 49. Machinist User.blueprint(:admin) do name { Sham.name + &quot; (admin)&quot; } admin { true } end User.make(:admin) <ul><li>saved associations for saved records, and unsaved associations for unsaved records
  50. 50. circumvents attr_protected </li></ul>
  51. 51. Factory Girl as Machinist require 'factory_girl/syntax/sham' require 'factory_girl/syntax/make' require 'factory_girl/syntax/blueprint' User.blueprint do name { 'John Smith' } email { 'john.smith@gmail.com' } end User.make(:name => 'Johnny') Sham.email { |n| &quot;somebody#{n}@example.com&quot; } # this isn't a real Sham!
  52. 52. Sweatshop <ul><li>http://github.com/mileszs/sweatshop/
  53. 53. don't confuse with dm-sweatshop!
  54. 54. generates factory girl or machinist configurations </li></ul>
  55. 55. DM-Sweatshop <ul><li>http://github.com/datamapper/dm-more/tree/master/dm-sweatshop
  56. 56. DataMapper only </li></ul>
  57. 57. DM-Sweatshop User.fixture {{ # or User.fix :username => (username = /w+/.gen), :email => &quot;#{username}@example.com&quot;, :password => (password = /w+/.gen), :pasword_confirmation => password }} User.fixture(:admin) {{ … }} User.generate :username => 'foo' # or User.gen User.generate(:admin) User.make # unsaved User.generate_attributes # hash
  58. 58. DM-Sweatshop User.fixture {{ :email_addresses => 10.of { EmailAddress.make } }} Marker.fixture {{ :categories => 5.of { Category.pick } # or: (2..8).of { Category.pick } }} User.fixture {{ :name => unique { something random }, :login => unique { |i| &quot;user#{i}&quot; } }}
  59. 59. Conclusions <ul><li>FixtureReplacement </li><ul><li>+ attr_protected
  60. 60. + String.random
  61. 61. – fewer features than the rest </li></ul><li>Object Daddy </li><ul><li>+ automatically creates associated objects
  62. 62. – no attr_protected
  63. 63. – no way to set one field based on another field </li></ul></ul>
  64. 64. Conclusions <ul><li>Factory Girl </li><ul><li>+ attr_protected
  65. 65. + sequences based on index
  66. 66. + setting fields based on other fields
  67. 67. + multiple factories for one class
  68. 68. + factory inheritance
  69. 69. + different syntax styles </li></ul></ul>
  70. 70. Conclusions <ul><li>Machinist </li><ul><li>+ a ttr_protected
  71. 71. + sequences based on index (but not inline)
  72. 72. + setting fields based on other fields
  73. 73. + Sham & Faker
  74. 74. + multiple factories for one class
  75. 75. – no factory inheritance </li></ul></ul>
  76. 76. Conclusions <ul><li>DM-Sweatshop </li><ul><li>+ sequences based on index
  77. 77. + unique (but non-deterministic) random values
  78. 78. + setting fields based on other fields
  79. 79. + multiple factories for one class
  80. 80. + generating values from regular expressions
  81. 81. + Model.pick
  82. 82. + 10.of
  83. 83. * DataMapper only
  84. 84. – no factory inheritance </li></ul></ul>

×