Fixture Replacement Plugins

download Fixture Replacement Plugins

If you can't read please download the document

Transcript of Fixture Replacement Plugins

  • 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

  • FixtureReplacement

14. Object Daddy 15. Factory Girl 16. Machinist 17. DM-Sweatshop 18. FixtureReplacement

  • http://replacefixtures.rubyforge.org/

19. FixtureReplacement (db/example_data.rb) module FixtureReplacement attributes_for :user do |u| password = String.random u.value= "a value", u.other= "other value", 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 => "overridden") 25. john = create_user(:username => "john") 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 => '[email protected]' 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 => SSNGeneratorend class SSNGenerator def self.next @last ||= '000-00-0000' @last = ("%09d" % (@last.gsub('-', '').to_i + 1)).sub(/^(d{3})(d{2})(d{4})$/, '1-2-3') end end 30. Object Daddy it "should have a comment for every forum the user posts to" do @user = User.generate @post = Post.generate @post.comments 'first post!!11') @user.should have(1).comments end admin_user = User.generate! do |user| user.activate! user.add_role("admin") 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.adminfalse f.email{ Factory.next(:email) } f.email { |u| "#{u.first_name}.#{u.last_name}@example.com" } end Factory.sequence :email do |n| "somebody#{n}@example.com" end f.sequence(:email) { |n| "person#{n}@example.com" } 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.admintrue 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 "only find approved posts" 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 '[email protected]' 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{ "My Company" }address{ "New York" } web{ "http://www.example.com" }email{ "[email protected]" } end business = Business.make invalid_business = Business.make(:email => "bad@email") 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| "20080101-#{index}" } 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 => "#{name} Shop") }# or just: business end 49. Machinist User.blueprint(:admin) do name{ Sham.name + " (admin)" } 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 { '[email protected]'} end User.make(:name => 'Johnny') Sham.email { |n| "somebody#{n}@example.com" } # 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=> "#{username}@example.com", :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| "user#{i}" } }} 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