我应该写这些测试用例吗?

时间:2017-06-23 08:42:08

标签: ruby-on-rails unit-testing testing

这是我要测试的模型的架构:

create_table "retreats", force: :cascade do |t|
  t.string   "title"
  t.string   "tagline"
  t.string   "type_of"
  t.datetime "created_at",                 null: false
  t.datetime "updated_at",                 null: false
  t.string   "description"
  t.string   "schedule"
  t.boolean  "available",   default: true
end

这是Retreat模型:

class Retreat < ApplicationRecord
  TYPES_OF_RETREATS = ['individual', 'group']
  validates :title, presence: true
  validates :type_of, presence: true, inclusion: {in: TYPES_OF_RETREATS,
    message: "%{value} is not a valid type."}

  has_many :testimonials, dependent: :destroy
  has_many :images, dependent: :destroy
  has_and_belongs_to_many :dates, class_name: "RetreatDate", foreign_key: 
   'retreat_id', association_foreign_key: 'retreat_date_id'
end

这些是我写过的测试用例:

  test "retreat should not save without a title" do
    retreat = retreats(:no_title)
    assert_not retreat.save, "Saved a retreat without a title"
  end

  test "retreat should not save without a type" do
    retreat = retreats(:no_type)
    assert_not retreat.save, "Saved a retreat without a type"
  end

  test "retreat can have a tagline, description, schedule and available" do
    retreat = retreats(:all_attributes)
    assert retreat.save, "Retreat failed to save"
  end

  test "retreat type should be from the provided list" do
    retreat = retreats(:invalid_type)
    assert_not retreat.save, "Some other retreat got saved. It shouldn't 
     have gotten saved."
  end

  test "retreat can have many testimonials" do
    retreat = retreats(:one)
    retreat.testimonials << Testimonial.new(statement: 'this is my 
      testimonial', participant_name: 'abc')
    assert retreat.save, "Retreat did not save with the testimonials."
  end

  test "retreat can have many dates" do
    retreat = retreats(:one)
    retreat.dates.create({date: '02-08-2012'})
    retreat.dates.create({date: '02-08-2013'})
    assert retreat.save, "Retreat with multiple dates is not saving"
    assert_equal(2, retreat.dates.length, "Retreat isn't saving multiple 
       dates.")
  end

我正在寻找有关我应该为哪种测试案例编写测试的建议。我觉得我的一些测试用例是不必要的。像验证的测试用例一样有意义,但测试我是否可以添加多个推荐书让我感到不舒服。

我可以像这样重写前两个测试用例:

test "retreat title and type_of must not be empty" do
  retreat = Retreat.new
  assert retreat.invalid?
  assert retreat.errors[:title].any?, "Title must exist"
  assert retreat.errors[:type_of].any?, "Type must exist"
end

编写单元测试的最佳实践是什么?我怎样才能编写更好的单元测试?

谢谢:)

2 个答案:

答案 0 :(得分:1)

http://www.betterspecs.org/帮助我开始测试。

我会坚持测试您的业务规则。因此,不要只测试保存或关系,因为这些工作并且是rails框架的一部分。但是测试规则如下:&#39;它可以有2个日期&#39; ,&#39;我希望能够同时创建一个撤退和见证&#39;

答案 1 :(得分:0)

我认为你需要一个更清晰的边界&#34;外部&#34;和&#34;内部&#34;。 Rails本身(或这方面的实际ActiveRecord)在这里没有帮助。它污染你的对象有很多责任,不清楚它们属于哪里:ActiveRecord不是最好的测试界面。

我遵循规则进行单元测试

  

仅测试自己的(公共)界面及其对直接协作者的影响。

write unit tests that test only the subject under test (internal) and never more的常见做法。被测试的主体,该单元与之合作的任何东西都是外部的:它们有自己独立的单元测试。

这会导致严重的嘲弄和磕磕碰碰。典型的非AR示例将是:

class BikeShed
  attr_accessor :color, :colorizer
  def initialize(color, colorizer = ColorFactory)
    @color = color
  end

  def discuss
    @color = ColorFactory.random
  end
end

测试看起来像:

class BikeShedTest < TestCase
  describe "#discuss" do
    it "changes color to a random color from ColorFactory" do
      subject.color_factory = stub("DefaultColor")
      subject.color_factory = color_factory
      color_factory.expects(:random).returns(color)

      subject.discuss

      assert_equal color, subject.color
    end
  end

  private

  def subject
    @subject ||= BikeShed.new
  end

  def color
    @color ||= stub("Color")
  end

  def color_factory
    @color_factory ||= stub("ColorFactory")
  end
end

我使用依赖注入传递其所有协作者,并仅测试主题以正确方式与此关联进行交互。而已。如初。

我使用BikeShed因为这是一个备受争议的主题;如果没有适当的整合测试,这种测试风格就会很糟糕,而且只会导致您只测试您已经正确设置了存根。它还可以导致测试实施&#34;很快。 然而,我真的很喜欢这种风格,因为它迫使你保持松耦合,并保持API和责任小,重点和清洁。有点像ActiveRecord中断。

ActiveRecord污染具有巨大责任堆的模型。验证,存储,回调,编组,映射到视图文件,有许多等,范围,缓存等等。

因此,w.r.t。 ActiveRecord(和大多数Rails对象),我遵循:

  

Everything Rails&#39;超类中的优惠是协作者

我认为ActiveRecord::Base好像是一些外部API。与上例中的ColorFactory一样。虽然这在技术上并不是真的正确:它也是我的API的一部分。考虑一下:如果你继承了Stripe::Payment,比如在你的MonthlyPayment中,你就不会测试Stripe是否正确地从你的CC获取资金,甚至连Strip都没有创建适当的付款在它的服务器上。那么为什么这与您的数据库有所不同? ActiveRecord只是数据库的网关,就像Stripe::Payment一样。

因此,在测试ActiveRecord时,我会考虑ActiveRecord提供的任何内容,作为外部API,我嘲笑它:

class ApplicationRecord < ActiveRecord::Base; end
class Retreat < ApplicationRecord
  validates :title, presence: true
  scope :nsfw -> { where("title LIKE '%nsfw%'") }
end

测试可能看起来像

class RetreatTest < TestCase
  describe ".nsfw" do
    it "selects only records whose title includes nsfw" do
      ActiveRecord::Base.expects(:where).with("title LIKE '%nsfw%'")
      subject.nsfw
    end
  end

  describe "#title" do
    it "is validated to be present" do
      subject.title = nil
      subject.validate
      assert_includes subject.errors["title"], "can't be blank"
    end
  end

  private

  def subject
    @subject ||= Retreat.new
  end
end

我们在这里看到三件重要的事情:

  1. 测试范围只是一种测试,用于确定我们使用正确的消息和参数调用外部 ActiveRecord API。我们可以放心地假设ActiveRecord已经进行了测试,以断言当我们正确调用它时,它将从某个存储返回正确的属性。这不是我们的责任。 (但是集成测试应该声明用户的最终结果是正确过滤的视图等)。
  2. 测试像<{1}}这样的只是测试我们已经正确配置了模型;不幸的是,用于确定此配置的API非常糟糕,因此我们会通过验证来确定它在我们的主题上设置了一定的错误。
  3. 唯一性验证更难,我们需要将validate删除以返回true,然后期望出现验证ActiveRecord.any?。例如。但我们永远不应该将记录写入数据库,然后使用该设置来确定对象的验证错误:这是脆弱的,我们正在测试很多完全不相关的东西。例如它被正确存储(AR的责任,而不是我们的模型)或AR使用正确的.error来确定唯一性(AR / ORM的责任,而不是我们的模型)。
  4. 请注意,这类似于&#34;测试实现,但实际上并非如此:我们正在测试以某种方式调用外部API。这是IMO单元测试的唯一任务。集成测试可以断言所有这些&#34;某些方式&#34;导致正确的行为。