工厂女孩201

最终产品图片
您将要创造的

关于该流行和有用的Ruby gem的第二篇文章讨论了一些更细微的话题,初学者在开始时就不必马上关注自己。 再次,我竭尽全力使它可以被新手访问,并解释了测试驱动开发(TDD)的每一个行话专家可能会遇到的麻烦。

主题

  • 相关属性
  • 瞬态属性
  • 惰性属性
  • 改造工厂
  • 回呼
  • 社团协会
  • 别名
  • 特质

相依属性

如果您需要使用属性值动态地组合其他工厂属性,则可以使用Factory Girl。 您只需要将属性值包装在一个块中并插入所需的属性即可。 这些块可以访问评估器 (由评估器提供) ,而后者又可以访问其他属性,甚至是瞬时属性。

FactoryGirl.define do

  factory :supervillain do
    name       'Karl Stromberg'
    passion    'marine biology'
    ambition   'human extinction'
    motivation 'save the oceans'
    profile    { "#{name} has a passion for #{passion} and aims to #{motivation} through #{ambition}."}
  end

end

villain = create(:supervillain)
villain.profile 
# => "Karl Stromberg has a passion for marine biology and aims to save the oceans through human extinction."

瞬态属性

我认为称它们为假属性是公平的。 这些虚拟属性还允许您在构造工厂实例时传递附加选项-当然是通过哈希。 实例本身不会受到它们的影响,因为不会在工厂对象上设置这些属性。 另一方面,Factory Girl将瞬态属性视为真实属性。

如果使用attributes_for ,它们将不会显示。 从属属性和回调能够访问工厂内部的这些伪造属性。 总体而言,它们是使工厂保持干燥的另一种策略。

FactoryGirl.define do

  factory :supervillain do

    transient do
      megalomaniac false
      cat_owner false
    end

    name       'Karl Stromberg'
    passion    'marine biology'
    ambition   'human extinction'
    motivation { "Building an underwater civilization#{" and saving the world" if megalomaniac}" }
    profile    { "Insane business tycoon#{" – friends with Blofeld" if cat_owner}" }
  end

end

villain = create(:supervillain)
villain.profile
# => "Insane business tycoon"
villain.motivation
# => "Building an underwater civilization"

cat_friendly_villain = create(:supervillain, cat_owner: true)
cat_friendly_villain.profile
# => "Insane business tycoon – friends with Blofeld"

narcissistic_villain = create(:supervillain, megalomaniac: true)
narcissistic_villain.motivation
# => "Building an underwater civilization and saving the world"

上面的示例变得更加干燥,因为无需为想要拯救世界或分别与布洛费尔德成为朋友的超级反派建立单独的工厂。 瞬态属性使您可以灵活地进行各种调整,并避免创建许多如此相似的工厂。

惰性属性

定义工厂时,将评估“工厂女孩”中的“普通”属性。 通常,您为与属性同名的方法提供静态值作为参数。 如果要将评估延迟到实例化实例的最后一个可能时刻,则需要通过代码块来提供属性的值。 从诸如DateTime对象之类的对象创建的关联和动态创建的值将是您最懒惰的客户。

FactoryGirl.define do

  factory :exploding_device do
  
    transient do
      countdown_seconds 10*60
      time_of_explosion { Time.now + countdown_seconds }
    end

    time_of_explosion { "Exploding in #{countdown_seconds} seconds #{time_of_explosion.strftime("at %I:%M %p")}" }
  end

end

ticking_device = create(:exploding_device)
ticking_device.time_of_explosion
# => "Exploding in 600 seconds at 11:53 PM"

修改工厂

这可能不是每天都会遇到的用例,但有时您会从其他开发人员那里继承工厂,并且想要更改它们(例如,如果您使用的是TDD宝石)。 如果您需要调整这些旧式工厂以更好地适合您的特定测试方案,则可以在不创建新工厂或使用继承的情况下对其进行修改。

您可以通过FactoryGirl.modify执行此FactoryGirl.modify ,它必须位于要更改的特定FactoryGirl.define块之外。 您不能做的是修改sequencetrait -但是您可以覆盖通过trait定义的属性。 “原始”工厂的回调也不会被覆盖。 Factory.modify块中的回调将仅在下一行中运行。

FactoryGirl.define do
  factory :spy do
    name              'Marty McSpy'
    skills            'Espionage and infiltration'
    deployment_status 'Preparing mission'
  end
end

FactoryGirl.modify do
  sequence :mission_deployment do |number|
    "Mission #{number} at #{DateTime.now.to_formatted_s(:short)}"
  end

  factory :spy do
    name            'James Bond'
    skills          'CQC and poker'
    favorite_weapon 'Walther PPK'
    body_count      'Classified'
    favorite_car    'Aston Martin DB9'
    deployment      { generate(:mission_deployment) }
	end
end

在上面的示例中,我们需要使间谍更加“复杂”,并使用更好的机制来处理部署。 我看到了一些示例,这些示例中gem的作者不得不以不同的方式处理时间,并且通过简单地覆盖需要调整的内容来方便地修改工厂对象。

回呼

回调使您可以在对象生命周期的不同时刻注入一些代码,例如saveafter_savebefore_validation等。 例如,Rails提供了很多功能,使新手很容易滥用此功能。

请记住,与对象持久性无关的回调是一种已知的反模式,因此最好不要越过这一线。 例如,在实例化诸如用户发送电子邮件或处理某些订单之类的东西之后,使用回调似乎很方便,但是这类事情会引起bug并创建不必要地难以重构的联系。 也许这就是为什么“只有女孩”为您提供五个回调选项供您使用的原因之一:

  • before(:create)保存工厂实例before(:create)执行一个代码块。 使用create(:some_object)时激活。
  • 保存工厂实例after(:create)执行代码块。 使用create(:some_object)时激活。
  • after(:build)工厂对象已建立在内存中after(:build)执行代码块。 在同时使用build(:some_object)create(:some_object)时激活。
  • 工厂创建存根对象after(:stub)执行代码块。 使用build_stubbed(:some_object)时激活。
  • custom(:your_custom_callback)执行自定义回调,而无需在beforeafter添加前缀。
FactoryGirl.define do
  
  factory :mission do
    objective        'Stopping the bad dude'
    provided_gadgets 'Mini submarine and shark gun'
    after(:build)    { assign_support_analyst }
  end

end
注意!

请注意,对于所有回调选项,在回调块内,您都可以通过block参数访问工厂实例。 每隔一段时间,这将派上用场,尤其是在关联方面。

FactoryGirl.define do

  factory :double_agent do
    after(:stub) { |double_agent| assign_new_identity(double_agent) }
  end

end

在下方,忍者拥有一堆令人讨厌的投掷星星(手里剑)。 由于回调中有ninja对象,因此您可以轻松地将投掷星号指定为属于忍者。 如果该示例给您留下了几个问号,请看一下有关关联的部分。

FactoryGirl.define do

  factory :ninja do
    name "Ra’s al Ghul"

    factory :ninja_with_shuriken do
      transient do
        number_of_shuriken 10
      end

      after(:create) do |ninja, evaluator|
        create_list(:shuriken, evaluator.number_of_shuriken, ninja: ninja)
      end
    end
  end

  factory :shuriken do
    name             'Hira-shuriken'
    number_of_spikes 'Four'
    ninja
  end

end

ninja = create(:ninja)
ninja.shurikens.length # => 0

ninja = create(:ninja_with_shuriken)
ninja.shurikens.length # => 10

ninja = create(:ninja_with_shuriken, number_of_shuriken: 20)
ninja.shurikens.length # => 20

另外,通过评估程序对象,您也可以访问临时属性。 这使您可以选择在“创建”工厂对象时需要传递其他信息,并且需要即时对其进行调整。 这为您提供了玩关联和编写表达性测试数据所需的所有灵活性。

如果发现工厂需要多个回调,则Factory Girl不会妨碍您-甚至是多个类型的回调。 当然,执行顺序是从上到下。

FactoryGirl.define do
  
	factory :henchman do
    name 'Mr. Hinx'
    after(:create) { |henchman| henchman.send_on_kill_mission }
    after(:create) { send_cleaner }
	end

end
FactoryGirl.define do
  
	factory :bond_girl do
    name 'Lucia Sciarra'
    after(:build)  { |bond_girl| bond_girl.hide_secret_documents  }
    after(:create) { close_hidden_safe_compartment }
	end

end

当然,细节在于魔鬼。 如果使用create(:some_object)create(:some_object)同时执行after(:build)after(:create)回调。

可以捆绑多个构建策略来执行相同的回调。

FactoryGirl.define do
  
	factory :spy do
    name 'Marty McFly'
    after(:stub, :build) { |spy| spy.assign_new_mission }
	end

end

最后但并非最不重要的一点是,您甚至可以设置“全局”回调之类的内容来覆盖所有工厂的回调,至少在该特定文件中(如果您已将它们分成多个工厂文件)。

factory / gun.rb

FactoryGirl.define do

  before(:stub, :build, :create) { |object| object.assign_serial_number }

	factory :spy_gun do
    name 'Walther PPK'
    ammunition '7.65mm Browning'
    association :owner

	  factory :golden_gun do
      name 'Custom Lazar'
      ammunition '24-carat gold bullet'
	  	after(:create) { |golden_gun| golden_gun.erase_serial_number } 
	  end
	end

end
注意!

如果使用继承来构成子工厂,则父级上的回调也将被继承。

最后一英里

在以下有关关联特征的部分 ,我们将它们放在一起-是的,我也潜入了别名,因为它是最好的地方,而不会四处走动。 如果您已经关注并记住了第一篇文章中的内容,那么现在所有内容都应该整齐地放入。

社团协会

关联对于每个具有一点点复杂性的自重Web应用程序都是必不可少的。 属于用户的帖子,具有很多评分的列表等等都是面包开发人员在一周中的任何一天吃早餐的地方。 从这个角度来看,很明显,对于更复杂的场景,工厂需要防弹并易于处理-至少是为了不弄乱您的TDD mojo。

我想说,通过Factory Girl模拟模型关联相对简单。 在我看来,这本身就是相当了不起的。 实现构建复杂数据集的高度便捷性使TDD的实践变得轻而易举,并且效率更高。

新版Q具有黑客技能,需要拥有一台像样的计算机,对吗? 在这种情况下,您有一个Computer类,并且其实例属于Quartermaster类的实例。 容易吧?

FactoryGirl.define do

  factory :quartermaster do
    name   'Q'
    skills 'Inventing stuff'
  end

  factory :computer do
    model 'Custom Lenovo ThinkPad W Series'
    quartermaster 
	end

end

那又有什么涉及呢? 假设我们的间谍使用的gun有多个cartridges (子弹)。

class Cartridge < ActiveRecord::Base 
  belongs_to :gun 
end

class Gun < ActiveRecord::Base
  has_many :cartridges
end
FactoryGirl.define do
	factory :cartridge do
    caliber '7.65' 
    gun
  end

	factory :gun do
    name 'Walther PPK'
    ammunition '7.65mm Browning'
    caliber    '7.65'

    factory :gun_with_ammo do
      transient do
        magazine_size 10
      end

      after(:create) do |gun, evaluator|
        create_list(:cartridge, evaluator.magazine_size, gun: gun)
      end
    end
  end
end

回调与关联非常方便,是吗? 现在,您可以制造带有或不带有弹药的枪支。 通过哈希gun: gun您提供了必要的信息,以便cartridge工厂通过foreign_key创建关联。

spy_gun = create(:gun)
spy_gun.cartridges.length # => 0

spy_gun_with_ammo = create(:gun_with_ammo)
spy_gun_with_ammo.cartridges.length # => 10

如果您需要其他杂志尺寸,则可以通过您的瞬时属性将其传递。

big_magazine_gun = create(:gun_with_ammo, magazine_size: 20)
big_magazine_gun.cartridges.length # => 20

那么不同的构建策略呢? 那里没有鱼吗? 好了,这是您需要记住的内容:如果对关联对象使用create ,则将同时保存它们。 因此, create(:quartermaster)将构建并保存Q和他的ThinkPad。

我最好使用build ,然后,如果我想避免访问数据库,对吗? 好主意,但是在我们的示例中, build仅适用于quartermaster -关联的computer仍将得到保存。 我知道有些棘手。 如果需要避免保存关联的对象,可以执行以下操作-为关联指定所需的构建策略。

FactoryGirl.define do

  factory :quartermaster do
    name   'Q'
    skills 'Inventing stuff'
  end

  factory :computer do
    model 'Custom Lenovo ThinkPad W Series'
    association :quartermaster, strategy: :build
	end

end

您可以命名关联的工厂对象,并使用构建策略传递哈希值。 您需要使用显式关联调用才能起作用。 下面的示例不起作用。

factory :computer do
  model 'Custom Lenovo ThinkPad W Series'
  quartermaster, strategy: :build
end

现在,两个对象都使用build而没有任何内容保存到数据库。 我们可以使用new_record?检查该假设new_record? ,如果实例未持久保存,则返回true

thinkpad = build(:computer)

thinkpad.new_record?               # => true
thinkpad.quartermaster.new_record? # => true

在进行此操作时,通过显式关联调用,您还可以引用不同的工厂名称并即时更改属性。

FactoryGirl.define do

  factory :quartermaster do
    name 'Q'
  end

  factory :computer do
    model 'Custom Lenovo ThinkPad W Series'
    association :hacker, factory: :quartermaster, skills: 'Hacking'
	end

end

让我们以多态的示例结束本章。

class Spy < ActiveRecord::Base
  belongs_to :spyable, polymorpic: true
end

class MIFive < ActiveRecord::Base
  has_many :spies, as: :spyable
end

class MISix < ActiveRecord::Base
  has_many :spies, as: :spyable
end
FactoryGirl.define do

  factory :mifive do
    name               'Military Intelligence, Section 5'
    principal_activity 'Domestic counter-intelligence'
  end

  factory :misix do
    name               'Military Intelligence, Section 6'
    principal_activity 'Foreign counter-intelligence'

  end

  factory :mifive_spy, class: Spy do
    name '005'
    association :spyable, factory: :mifive 
  end

  factory :misix_spy, class: Spy do
	  name '006'
    association :spyable, factory: :misix
  end

end

# MI5 agents
mifive = create(:mifive)
mifive_spy = create(:mifive_spy)
mifive.spies << mifive_spy

mifive.name             # => "Military Intelligence, Section 5"
mifive_spy.name         # => '005'
mifive.spies.length     # => 1
mifive.spies.first.name # => '005'


# MI6 agents
misix = create(:misix)
misix_spy_01 = create(:misix_spy, name: '007')
misix_spy_02 = create(:misix_spy)
misix.spies << misix_spy_01
misix.spies << misix_spy_02

misix.name              # => "Military Intelligence, Section 6"
misix.spies.length      # => 2
misix_spy_01.name       # => '007'
misix_spy_02.name       # => '006'
misix.spies.first.name  # => '007'

如果这需要更多的时间,不要感到难过。如果您不确定这里发生了什么,我建议赶上多态关联

别名

工厂的别名使您可以更充分地表达使用工厂对象所处的上下文。您只需要提供替代名称的哈希即可更好地描述关联对象之间的关系。

假设您有一个:agent工厂和一个:law_enforcement_vehicle工厂。 在这些汽车的上下文中将代理人称为:owner会很好吗? 在下面的示例中,我将其与没有别名的示例进行了对比。

FactoryGirl.define do

  factory :agent, aliases: [:owner] do
    name   'Fox Mulder'
    job    'Chasing bad dudes'
    special_skills 'Investigation and intelligence'
    
    factory :double_O_seven do
      name 'James Bond'
    end
  end
 
  factory :law_enforcement_vehicle do
    name 'Oldsmobile Achieva'
    kind 'Compact car'
    :owner
  end

  factory :spy_car do
    name 'Aston Martin DB9'
    kind 'Sports car'
    double_O_seven
  end

end
注意!

在工厂中将它们用于关联时,请不要忘记在别名工厂( :owner )前面添加冒号。 在这种情况下,文档和许多博客文章都使用冒号而不加冒号。 您得到的可能只是一个NoMethodError因为您现在缺少该别名的setter方法。 (我最好打开一个请求请求。)第一次遇到这个问题时,我感到困惑,花了我一些时间才能通过。 请记住,有时会选择性地不信任文档和博客文章。 当然,您也是如此。

我想您会同意,使用别名不仅可以更好地阅读,而且可以为您或在您之后的人提供有关所讨论对象的更多上下文。 是的,如果您只有一个:aliases也需要使用复数:aliases

您也可以写出一些不同的方式-更详细些。

factory :agent, aliases: [:mulder] do
  name   'Fox Mulder'
  job    'Chasing bad dudes'
  special_skills 'Investigation and intelligence'
end  

factory :law_enforcement_vehicle do
  name 'Oldsmobile Achieva'
  kind 'Compact car'
  association :owner, factory: :agent
end

好吧,不是那么整洁吧?

当然,您也可以使用这些别名立即“构建”工厂对象。

fbi_agent = create(:mulder)
fbi_agent.name # => 'Fox Mulder'

在注释的上下文中, :user可以称为:commenter ,在:crime的情况下, :user可以别名为:suspect ,依此类推。 并不是真正的火箭科学,更像是方便的语法糖,它可以减少重复的诱惑。

特质

这是我对《工厂女郎》最喜欢的事情之一。 简而言之,特质就像是乐高玩具般的砌块,可以用来建造工厂并融入行为。 它们是您要添加到特定工厂的符号特征/属性的逗号分隔列表,并且它们也在工厂的文件中定义。

在我看来, trait是保持工厂数据DRY的同时保持表现力的最强大,最方便的功能。 它使您可以将属性组捆绑在一起,为它们分配单独的名称,然后随时随地重复使用它们。 还记得我敦促您定义准工厂对象吗? 特质将帮助您在不牺牲任何便利的情况下准确实现这一目标。

FactoryGirl.define do
    
  factory :spy_car do
    model         'Aston Martin DB9'
    top_speed     '295 km/h'
    build_date    '2015'
    ejection_seat true

    trait :submarine do
      ejection_seat              false
      water_resistant            '100 m'
      submarine_capabilities     true
      air_independent_propulsion true
    end
    
    trait :weaponized do
      rockets           true
      number_of_rockets '12'
      machine_gun       true
      rate_of_fire      '1,500 RPM'
      tank_armour       true
    end
    
    trait :cloaked do
      active_camouflage true
      radar_signature   'reduced'
      engine            'silenced'
    end
    
    trait :night_vision do
      infrared_sensors true
      heads_up_display true
    end
  end

end

如您所见,如果要更改分布在多个对象上的某些属性,则可以在一个中央位置进行。 无需shot弹枪手术。 通过特征管理状态再方便不过了。

通过这种设置,您可以通过混合各种属性捆绑包来构建精美的间谍车,而无需通过创建各种新工厂来复制任何东西,这些工厂可以满足您所需的所有选项。

invisible_spy_car = create(:spy_car, :cloaked, :night_vision)
diving_spy_car    = create(:spy_car, :submarine, :cloaked)
tank_spy_car      = create(:spy_car, :weaponized, :night_vision)

您可以将traits与createbuildbuild_stubbedattributes_for 。 如果Q变得更聪明,您还可以通过传入散列来同时覆盖各个属性。

build(:spy_car, :submarine, ejection_seat: true)

对于在特定工厂频繁发生的特征组合,您还可以创建子工厂,其名称最能代表各种数据集组合。 这样一来,与创建测试数据时的所有时间相反,您只需捆绑一次它们的特征。

FactoryGir.define do

  factory :spy_car do
    model         'Aston Martin DB9'
    top_speed     '295 km/h'
    build_date    '2015'
    ejection_seat true

    trait :submarine do
      ...
    end
    
    trait :weaponized do
      ...
    end
    
    trait :cloaked do
      ...
    end
    
    trait :night_vision do
      ...
    end
  end

    factory :invisible_spy_car, traits: [:cloaked, :night_vision]
    factory :diving_spy_car,    traits: [:submarine, :cloaked]
    factory :tank_spy_car,      traits: [:weaponized, :night_vision]
    factory :ultimate_spy_car,  traits: [:cloaked, :night_vision, :submarine, :weaponized]

end

这使您可以更简洁地创建这些对象,并且也更具可读性。

build_stubbed(:invisible_spy_car)
create(:ultimate_spy_car)

代替:

build_stubbed(:spy_car, :cloaked, :night_vision)
create(:spy_car, :cloaked, :night_vision, :submarine, :weaponized)

阅读好多了,不是吗? 特别是在不涉及变量名的情况下。

您甚至可以将特征重新用作其他特征和工厂的属性。 当然,如果为多个特征定义相同的属性,则最后定义的优先。

FactoryGirl.define do

  factory :spy_car do
    model         'Aston Martin DB9'
    top_speed     '295 km/h'
    build_date    '2015'
    ejection_seat true

    trait :submarine do
      ...
    end
    
    trait :weaponized do
      ...
    end
    
    trait :cloaked do
      ...
    end
    
    trait :night_vision do
      ...
    end

    trait :mobile_surveillance do
      cloaked
      night_vision
      signal_detector      true
      signal_analyzer      true
      wifi_war_driver      true
      license_plate_reader true
      mini_drone           true
    end
  end

  factory :ultimate_spy_car, parent: :spy_car do
    car_plane true
    submarine
    weaponized
    mobile_surveillance
  end

end

讲究mobile_surveillance特质,这重用了cloakednight_vision特征,基本上为属性。 另外, ultimate_spy_car工厂,我分离出的的spy_car厂家定义为乐趣此时,重用所有特质加上一个额外的属性,使得它飞得。 纯电影魔术,或者也许我应该说是《工厂女郎》魔术。

create_listbuild_list也可以使用特征。 第二个参数必须是所需的工厂实例数。

create_list(:spy_car, 3, :night_vision)
build_list(:spy_car, 4, :submarine, :cloaked)

使用具有特征的关联会很酷吗? 当然,您可以将回调和关联整齐地打包到特征中。 h!

FactoryGirl.define do

  factory :cartridge do
    kind       'Small calliber pistol ammunition'
    caliber    '7.65'
    projectile 'Lead'
    gun
  
    factory :golden_cartridge do
      projectile 'Gold'
      association :gun, :golden
    end
  end
  
  factory :gun do
    name 'Walther PPK'
    ammunition '7.65mm Browning'
    caliber    '7.65'
  
    transient do
      magazine_size 10
    end
  
    trait :golden do
      name 'Custom Lazar'
      ammunition '23-carat gold bullet'
    end
  
    trait :with_ammo do
      after(:create) do |gun, evaluator|
        create_list(:cartridge, evaluator.magazine_size, gun: gun)
      end
    end
  
    trait :with_golden_ammo do
      after(:create) do |golden_gun, evaluator|
        create_list(:golden_cartridge, evaluator.magazine_size, gun: golden_gun)
      end
    end
  end
end

现在如何使用它们应该很无聊。

cartridge = create(:cartridge)
cartridge.projectile     # => 'Lead'
cartridge.gun.name       # => 'Walther PPK'
cartridge.gun.ammunition # => '7.65mm Browning'
cartridge.gun.caliber    # => '7.65'

golden_cartridge = create(:golden_cartridge)
golden_cartridge.projectile     # => 'Gold'
golden_cartridge.gun.name       # => 'Custom Lazar'
golden_cartridge.gun.ammunition # => '23-carat gold bullet'
golden_cartridge.gun.caliber    # => '7.65'

gun_with_ammo = create(:gun, :with_ammo)
gun_with_ammo.name                        # => 'Walther PPK'
gun_with_ammo.ammunition                  # => '7.65mm Browning' 
gun_with_ammo.cartridges.length           # => 10
gun_with_ammo.cartridges.first.projectile # => 'Lead'
gun_with_ammo.cartridges.first.caliber    # => '7.65'

golden_gun_with_golden_ammo = create(:gun, :golden, :with_golden_ammo)
golden_gun_with_golden_ammo.name                        # => 'Custom Lazar'
golden_gun_with_golden_ammo.ammunition                  # => '24-carat gold bullet' 
golden_gun_with_golden_ammo.cartridges.length           # => 10
golden_gun_with_golden_ammo.cartridges.first.projectile # => 'Gold'
golden_gun_with_golden_ammo.cartridges.first.caliber    # => '7.65'

最后的想法

智慧的最后一句话:更改是您始终不变的伴侣-更改属性或数据类型的需求始终存在。 像这样的设计决策会不断演变。 特质将减轻痛苦并帮助您管理数据集。

想象一下,如果您使用选项哈希进行实例化,并且该要求已完全更改。 您的测试中有多少个潜在位置可能会损坏,现在需要注意? 直截了当, trait是消除测试套件中重复项的非常有效的工具。 但是,借助所有这些方便,不要懒惰,并且忘记在由特征表示的列上进行单元测试! 这样,您就可以为它们提供与有效对象所需的准系统属性相同的照顾。

在Factory Girl中还有更多发现,我相信您现在有足够的能力在需要时将它们组合在一起。 玩这个宝石玩得开心。 我希望您的TDD习惯将从中受益。

翻译自: https://code.tutsplus.com/articles/factory-girl-201--cms-25171

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值