标准的#index 页面,列出记录集
let!(:photos_list) { FactoryGirl.create_list(:photo, 3) }
visit photos_path
photos_list.each do |photo|
expect(page).to have_content photo.name
end
感觉太女汉子了,求温柔点的写法
RSpec 可以帮助我们从不同的层级来编写测试代码,这其中 feature spec 是非常接近于验收测试的一层,而验收测试的要点就是(尽量)不牵扯底层的逻辑,(尽量)模拟用户的交互来涵盖一系列的系统内部的实现。换言之,在观察 feature spec 用例的时候,应当看不到明显的底层代码(无论这个底层的层级有多低,view -> controller -> model ...)
之所以加了“尽量”两个字,是因为在现实情况中完全不去碰底层的业务逻辑也比较困难。好比你这个例子,要想测试“用户在访问 "/photos" 路径的时候能看到 "photos" 的名字,那么至少也得先创建一些 "photos"。所以一开始你去 create_list
是没有错的。
问题在于后面两个部分,我们分开说:
visit photos_path
可以改成 visit '/photos'
——如果你的 routes 是这么映射的话——因为验收测试往往是给非开发人员,比如专职的测试人员,甚至是上司或客户看的(在这个层面上,Cucumber 的语法更合适一些)。对这些人来说,他们很可能不懂 Rails,他们也不关心 photos_path
到底是什么。在他们的大脑中,对于这个测试的理解是这样的:
假设目前系统内有 3 张相片,那么当用户访问”/photos" URI 的时候,应该能看到这 3 张相片的名字。
因此,visit '/photos'
更加清楚,更加明确。在 feature spec 这个层级上,测试并不关心像 '/photos' 这样的路径在底层是如何映射的,这就是(尽量)不牵扯底层的逻辑,或者说不暴露底层的实现细节。
再看第二个部分:
photos_list.each do |photo|
expect(page).to have_content photo.name
end
可以写成(我假设了 FactoryGirl 里相片的名字):
expect(page).to have_content 'Photo 1'
expect(page).to have_content 'Photo 2'
expect(page).to have_content 'Photo 3'
你没看错哦,就是这么直白的写法。如果你觉得不够优雅可以 ['Photo 1', 'Photo 2', 'Photo3'].each { |name| expect(page).to have_content name }
这样包装一下,不过测试代码更偏向于简单直接,适当的 repeat 完全可以接受。再说这个用例本来的目的就是为了证明访问 '/photos' 时可以看到这三个名字,所以这么写完全没错。
最后,你可以把最开始的那句,也就是 let!(photo_list)
提取出去。变成一个助手方法也好,或者放在 background
(an alias for :before
)也好,而且你无须创建 photo_list
这个变量,因为在用例里根本用不到。这样一来,用例本身就完全不依赖底层的代码逻辑了,输入是访问 URI,输出是页面上的字符,业务逻辑都在黑盒里,用户完全不关心,也不会注意到。
Feature spec 是 RSpec + Capybara 提供的最高层级的测试,也是抽象程度最高的一级(Capybara 为什么要求开发者不要继续使用在 request spec?因为 request spec 还是有点“底层”了,而 Capybara 的目的是操作/查询用户界面上的元素,因此还需要向高抽象一层,于是才出现了 feature spec。)。若是践行测试驱动开发的理论,那么应该先从这一层开始,然后更随其失败的提示,随着细节的逐步深入,再用更深更细的测试去覆盖系统中不同层面的代码和业务逻辑。
@nightire 请问一下,最近看到很多人转向用https://github.com/paulelliott/fabrication ,不知这东西和 FactoryGirl 相比,有什么明显的优势?谢谢
http://stackoverflow.com/questions/12179107/comparing-factory-girl-with-fabrication
Fabrication 的好處是生物件不會 hit db ...Factory.build_stubbed 還不一定....
#5 楼 @xdite 最近在上@knwang 的课,里面用的是 Fabrication,他也说他在自己的项目中也是使用这个。因为自己也读了https://leanpub.com/everydayrailsrspec-cn 用的是 FactoryGirl,写法上都差不多,所以一直都用 FactoryGirl。感觉会不会 Fabrication 更加轻量,并对多种数据库支持更好一些呢。