Your SlideShare is downloading. ×
Fixture Replacement Plugins
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Saving this for later?

Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime - even offline.

Text the download link to your phone

Standard text messaging rates apply

Fixture Replacement Plugins

2,057
views

Published on

Published in: Technology, Business

0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,057
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
4
Comments
0
Likes
2
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Fixture replacement plugins Jakub Suder Lunar Logic Polska
  • 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. Why fixtures aren't enough
    • not readable – hard to remember which record has what properties
    • 4. no one remembers the „story”
    it "should be possible to delete a folder only in a group that you're a member of" 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. Why fixtures aren't enough
    • information scattered over many files
    • 6. fixtures can be invalid
    • 7. some things are much more difficult than they should be
    • 8. not DRY
    • 9. slow?
  • 10. Why fixtures aren't enough
    • it can get complicated...
    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. Solutions
    • object generator?
    def create_event(options = {}) Event.create({:name => 'Event 1', :location => 'Krakow', :date => Date.today}).merge(options) end
    • too simple, difficult to extend
      • d oesn't handle associations well
      • 12. doesn't handle uniqueness
  • 13. Solutions
  • 18. FixtureReplacement
    • http://replacefixtures.rubyforge.org/
  • 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. FixtureReplacement
    • String.random
    • 21. new_user -> User.new
    • 22. create_user -> User.create!
    • 23. default_user -> for use inside model_attributes definitions
    • 24. new_user(:thing => &quot;overridden&quot;)
    • 25. john = create_user(:username => &quot;john&quot;)
    • 26. circumvents attr_protected by assigning manually
  • 27. Object Daddy
    • http://github.com/flogic/object_daddy/
  • 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. 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. 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. Object Daddy
    • /spec/exemplars/user_exemplar.rb
    • 32. automatically creates objects for required relationships
    • 33. relationships are always saved, regardless if you use spawn or generate
  • 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. 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. 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. 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. Factory Girl
    • test/factories.rb, spec/factories.rb
    • 39. test/factories/*.rb, spec/factories/*.rb
    • 40. saved associations for saved records, and unsaved associations for unsaved records
    • 41. circumvents attr_protected
  • 42. Factory Girl
    • Factory Girl as Object Daddy :)
    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. Machinist
    • http://advent2008.hackruby.com/past/2008/12/05/machinist_for_your_test_data_factory _
    • 44. http://toolmantim.com/articles/fixtureless_datas_with_machinist_and_sham
    • 45. http://github.com/hassox/machinist/tree/master
  • 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. 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. Machinist User.blueprint do name { Sham.name } email { Sham.email } business { Business.make(:name => &quot;#{name} Shop&quot;) } # or just: business end
  • 49. Machinist User.blueprint(:admin) do name { Sham.name + &quot; (admin)&quot; } admin { true } end User.make(:admin)
    • saved associations for saved records, and unsaved associations for unsaved records
    • 50. circumvents attr_protected
  • 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. Sweatshop
    • http://github.com/mileszs/sweatshop/
    • 53. don't confuse with dm-sweatshop!
    • 54. generates factory girl or machinist configurations
  • 55. DM-Sweatshop
    • http://github.com/datamapper/dm-more/tree/master/dm-sweatshop
    • 56. DataMapper only
  • 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. 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. Conclusions
    • FixtureReplacement
      • + attr_protected
      • 60. + String.random
      • 61. – fewer features than the rest
    • Object Daddy
      • + automatically creates associated objects
      • 62. – no attr_protected
      • 63. – no way to set one field based on another field
  • 64. Conclusions
    • Factory Girl
      • + attr_protected
      • 65. + sequences based on index
      • 66. + setting fields based on other fields
      • 67. + multiple factories for one class
      • 68. + factory inheritance
      • 69. + different syntax styles
  • 70. Conclusions
    • Machinist
      • + a ttr_protected
      • 71. + sequences based on index (but not inline)
      • 72. + setting fields based on other fields
      • 73. + Sham & Faker
      • 74. + multiple factories for one class
      • 75. – no factory inheritance
  • 76. Conclusions
    • DM-Sweatshop
      • + sequences based on index
      • 77. + unique (but non-deterministic) random values
      • 78. + setting fields based on other fields
      • 79. + multiple factories for one class
      • 80. + generating values from regular expressions
      • 81. + Model.pick
      • 82. + 10.of
      • 83. * DataMapper only
      • 84. – no factory inheritance