这是我要测试的模型的架构:
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
编写单元测试的最佳实践是什么?我怎样才能编写更好的单元测试?
谢谢:)
答案 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
我们在这里看到三件重要的事情:
validate
删除以返回true,然后期望出现验证ActiveRecord.any?
。例如。但我们永远不应该将记录写入数据库,然后使用该设置来确定对象的验证错误:这是脆弱的,我们正在测试很多完全不相关的东西。例如它被正确存储(AR的责任,而不是我们的模型)或AR使用正确的.error
来确定唯一性(AR / ORM的责任,而不是我们的模型)。请注意,这类似于&#34;测试实现,但实际上并非如此:我们正在测试以某种方式调用外部API。这是IMO单元测试的唯一任务。集成测试可以断言所有这些&#34;某些方式&#34;导致正确的行为。