CSC/ECE 517 Spring 2014/ch1a 1k wm: Difference between revisions
(→Example: added some narrative about why DRY should hold for fixtures) |
|||
(One intermediate revision by the same user not shown) | |||
Line 9: | Line 9: | ||
Factory Girl's purpose is to expedite good testing by creating test fixtures for the developer, eliminating the need to create large fixtures by hand. There are two versions, one for use with regular Ruby (factory_girl) and one for use with Ruby on Rails (factory_girl_rails<ref>https://github.com/thoughtbot/factory_girl_rails</ref>) | Factory Girl's purpose is to expedite good testing by creating test fixtures for the developer, eliminating the need to create large fixtures by hand. There are two versions, one for use with regular Ruby (factory_girl) and one for use with Ruby on Rails (factory_girl_rails<ref>https://github.com/thoughtbot/factory_girl_rails</ref>) | ||
'''Test fixtures''' are used to create an application state through which you can test code.<ref>http://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures</ref> | '''Test fixtures''' are used to create an application state through which you can test code.<ref>http://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures</ref> In some cases, the application state you need to build is very simple. For example, in unit testing you are testing very small pieces of code, the state that you need to set up is very small (typically one or two instances of a single model class). In other cases the application state may be complicated and the data handled very large, in which case the fixture will also be very large. Large fixtures could be used in performance testing in order to load the system up and tax its resources. | ||
==Generic Examples== | ==Generic Examples== | ||
Line 154: | Line 154: | ||
However, this is harder to maintain and is not very DRY-friendly. Instead, we could use Factory Girl and expedite the process, also making it more DRY while we're at it (using some Factory Girl features not shown above)! | However, this is harder to maintain and is not very DRY-friendly: it's very repetitious and you must copy the entire block any time you need to put 1000 users into a fixture. Not only that, but if you did copy the code block, if at any point the class changes you'd have to go back and change all the places you copied that code. Instead, we could use Factory Girl and expedite the process, also making it more DRY while we're at it (using some Factory Girl features not shown above)! | ||
<nowiki>FactoryGirl.define do | <nowiki>FactoryGirl.define do | ||
sequence :email do |n| | sequence :email do |n| | ||
Line 180: | Line 180: | ||
built_users_10 = build_list(:user, 10) #make 10 more users | built_users_10 = build_list(:user, 10) #make 10 more users | ||
</nowiki> | </nowiki> | ||
By abstracting away the creation of unique IDs for our users and giving us a factory through which to create a list of users, we need not worry about the implementation of making a group of users in our fixtures. If at any time the class changes, we need only change the file that has the factory in it, all calls to the factory remain unchanged. | |||
==References== | ==References== | ||
<references /> | <references /> |
Latest revision as of 20:59, 21 February 2014
Factory Girl
Background
From Factory Girl's Ruby Toolbox page<ref>https://www.ruby-toolbox.com/projects/factory_girl</ref>:
factory_girl provides a framework and DSL for defining and using factories - less error-prone, more explicit, and all-around easier to work with than fixtures.
Also from the Factory Girl's github page<ref>https://github.com/thoughtbot/factory_girl</ref>:
factory_girl is a fixtures replacement with a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute hashes, and stubbed objects), and support for multiple factories for the same class (user, admin_user, and so on), including factory inheritance.
Factory Girl's purpose is to expedite good testing by creating test fixtures for the developer, eliminating the need to create large fixtures by hand. There are two versions, one for use with regular Ruby (factory_girl) and one for use with Ruby on Rails (factory_girl_rails<ref>https://github.com/thoughtbot/factory_girl_rails</ref>)
Test fixtures are used to create an application state through which you can test code.<ref>http://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures</ref> In some cases, the application state you need to build is very simple. For example, in unit testing you are testing very small pieces of code, the state that you need to set up is very small (typically one or two instances of a single model class). In other cases the application state may be complicated and the data handled very large, in which case the fixture will also be very large. Large fixtures could be used in performance testing in order to load the system up and tax its resources.
Generic Examples
All examples taken from the github project<ref>https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md</ref>
Factories
Factories are the basic unit of Factory Girl, they wrap up a constructor into a single method call that can be used in multiple test fixtures. A factory looks pretty similar to a normal constructor but can be used multiple times without having to specify any parameters, as shown below.
FactoryGirl.define do factory :user do first_name "John" last_name "Doe" admin false end end
The above creates a factory for class User (inferred by Factory Girl from the name of the factory) that can then be called in many different ways. You can also define factories using specific classes, not only inferred from the name of the factory, as below (in the same word as the above example).
FactoryGirl.define do factory :admin, class: User do first_name "Admin" last_name "User" admin true end end
Using Factories
You can use factories in a variety of ways, as shown. You can also overwrite predefined attributes on the fly by passing in a hash of attributes and values.
# Returns a User instance that's not saved user = build(:user) # Returns a saved User instance user = create(:user) # Returns a hash of attributes that can be used to build a User instance attrs = attributes_for(:user) # Returns an object with all defined attributes stubbed out stub = build_stubbed(:user) # Passing a block to any of the methods above will yield the return object create(:user) do |user| user.posts.create(attributes_for(:post)) end # Build a User instance and override the first_name property user = build(:user, first_name: "Joe") user.first_name # => "Joe"
Aliasing
You can utilize aliases to make somewhat more specialized factories out of existing factories, as shown.
factory :user, aliases: [:author, :commenter] do first_name "John" last_name "Doe" date_of_birth { 18.years.ago } end factory :post do author # instead of # association :author, factory: :user title "How to read a book effectively" body "There are five steps involved." end factory :comment do commenter # instead of # association :commenter, factory: :user body "Great article!" end
The above code creates a factory called user and then uses that factory to add users as attributes of post and comment with a different name (author and commenter).
Lazy, Dependent, and Transient Attributes
Attributes need not be statically defined. There are several other ways to define attributes of factories, all termed lazy attributes because they acquire a value after the object has been instantianted. The simplest case of lazy attributes is the simple lazy attribute, determined by a block rather than given as a static value.
factory :user do # ... activation_code { User.generate_activation_code } date_of_birth { 21.years.ago } end
Dependent attributes are lazy attributes that use the values of other attributes in the factory to determine their value.
factory :user do first_name "Joe" last_name "Blow" email { "#{first_name}.#{last_name}@example.com".downcase } end create(:user, last_name: "Doe").email # => "joe.doe@example.com"
Finally, transient attributes allow you to further modify factories and prevent repetition of code, in a way passing arguments to the factory creation function.
factory :user do ignore do rockstar true upcased false end name { "John Doe#{" - Rockstar" if rockstar}" } email { "#{name.downcase}@example.com" } after(:create) do |user, evaluator| user.name.upcase! if evaluator.upcased end end create(:user, upcased: true).name #=> "JOHN DOE - ROCKSTAR"
Further Reading
Factory Girl contains far more than can be expressed in so verbose a fashion in a summary page, so be sure to read the entirety of the Getting Started guide. This guide goes through all of the above and more, in detail, with code examples.
Using with Ruby on Rails
As mentioned earlier, Factory Girl can be used in Ruby on Rails through the factory_girl_rails gem. All of the same benefits apply, including those not mentioned above.
Example
A typical test scenario is benchmarking the time a sort takes to complete on a random assortment of objects. A stock Rake test fixture implementation might look something like the following:
person1: id: 1 name: Bob dob: 10-11-1990 person2: id: 2 name: George dob: 9-8-1990 person3: id: 3 name: Stanley dob: 1-9-1990
This could get tedious with more than 3 values, so you could use ERb (Embedded Ruby)
<% for i in 1..1000 %> fix_<%= i %>: id: <%= i %> name: guy_<%= 1 %> dob: <%= Date.today.strftime("%Y-%m-%d") %> <% end %>
However, this is harder to maintain and is not very DRY-friendly: it's very repetitious and you must copy the entire block any time you need to put 1000 users into a fixture. Not only that, but if you did copy the code block, if at any point the class changes you'd have to go back and change all the places you copied that code. Instead, we could use Factory Girl and expedite the process, also making it more DRY while we're at it (using some Factory Girl features not shown above)!
FactoryGirl.define do sequence :email do |n| "person#{n}@example.com" end sequence :id do |n| #{n} end sequence :dob do |n| (20.years.ago)+(Random.rand(20).months)#give random DOB from 20 years ago + 0..20 months end factory :user do id first_name "John" last_name "Doe" email dob end end built_users_2500 = build_list(:user, 2500) #make 2500 users with unique email addresses and id's and random DOB built_users_10 = build_list(:user, 10) #make 10 more users
By abstracting away the creation of unique IDs for our users and giving us a factory through which to create a list of users, we need not worry about the implementation of making a group of users in our fixtures. If at any time the class changes, we need only change the file that has the factory in it, all calls to the factory remain unchanged.
References
<references />