从上层功能开始测试交互, 然后 drill down to the nitty gritty? 从底层功能逐步测试到最终的feature? The answer to this depends, and will vary person-to-person and feature-to-feature. 这个问题的答案取决于个人和特征。
这种方法可以plays to the strength of each type of test 发挥优势, minimize the downsides they have(such as slow run times)降低劣势.
pyramid test
Feature Specs 功能规格测试 如果你是outside-in的测试模式,那就是从功能测试开始. 比如我们的第一个功能是运行用户创建一个link post. 要做到这个,他们必须在home page上点一个link, fill in the title and URL of the link.然后click "SUbmit". 我们会测试,一旦他做了这些后,他会进入到一个新页面看到title是他们输入的,link也是他们提供的. 比如: As a user when I visit the home page And I click "Submit a link post" And I fill in my title and URL And I click "Submit" THen I should see the title on the page And it shoud link to the given URL
有一点你要注意,我们开始描述的 who 就是终端用户, 我们的系统只有一种角色, 用user是安全的. 有很多应用程序需要多个, 未认证的用户,管理员,指定的角色如教练运动员等.
Capybara 是为了和浏览器交互的工具, Capybara通过api和浏览器交互,提供方法访问pages,填forms,click buttons and More... bundle add 'capybara' require it from your rails_helper require "capybara/rails"
安装capybara后,我们继续把这个功能测试写完: # spec/features/user_submits_a_link_spec.rb require "rails_helper" RSpec.feature "User submit a link" do scenario "they see the page for the submitted link" do link_title ="This Testing Rails book is awesome!" link_url = "http://testingrailsbook.com"
visit root__path click_on "Submit a new link" fill_in "link_title", with: link_title fill_in "link_url", with: link_url click_on "Submit!"
expect(page).to have_link link_title, herf: link_url end end
.feature 是Capybara提供的一个方法
调用这个方法可以让你进入capybara的和page交互的方法, feature是全局的上下文. feature takes a string, 用来描述你的feature. 可以和文件名字一样. 像文档的格式.
scenario "" do end 这是一个单独的specification. 描述一个可能的创建方式. scenario的描述 要和 feature的有关联性, 当他们一起读的时候,应该像是一句话, 这样运行spec的时候,读起来well.
click_on and fill_in 也来自 capybara, fill_in 会find a method by it's name, id or label and fills it in with the given text. 如果是用id, 要在前面加#. 我们知道 rails给所有的fields定义ids,通过model的名字,我们先不定义这些id, 让rails发挥他的优势.We know that Rails gives ids to all fields by joining the model name and the field name. As long as we don’t customize those, we can use them to our advantage.
魔幻的地方在这里: 其实没有 have_link?方法. RSpec定义了 has_ 开头 ? 结尾的方法. 而Capybara定义了Page的 has_link?方法. 如果我写成了 page.has_link? 那要怎么搞? RSpec will automatically look for the method #has_link? when it sees #have_link .
这里有点复杂,如果你写大量的测试,熟悉起来后,这种就会好点.
运行测试 rspec path/your_spec.rb
打印的结果, is called documentation . 运行一个结果文档展现的比较好看,但是如果运行很多这样显示就是cumbersome 大而笨重.
This section outlines all of the failures. outlines 概括了.
Randomized with seed 5573 结果的最后会显示这么一句.我们运行我们的specs in a random order to help diagnose specs that may not clean up after themselves properly.
无论如何, it is imperative (重要紧急的) failed的test才是你要写的代码.如果你不能预测到failure message, 你应该运行tests.
还要写点错误的用例,来测试对错误的输入的反应是否正确.
#have_content . Like #have_link , this method comes from Capybara, and is actually #has_content? . #has_content? will look on the page for the given text, ignoring any HTML tags.
问题是fixures定义在测试外, 找问题的时候, 需要hunt down(追捕到) another file to be able to understand the entirety of what is happening. as applications grow.需要各种variations在每个models对应不同的situations. fixtures的数量可能会比较多.各种用例要覆盖到,那就要造各种数据.
FactoryBot Rather than defining hardcoded data, factories 定义分类,预定义必要的逻辑,当你instantiating实例化的时候,可以覆盖变量. like this:
rails generate factory_bot:model Link title:string url:string insert spec/factories.rb
cat spec/factories.rb
FactoryBot.define do
factory :link do
title { "MyString" }
url { "http://MyString/" }
end
end
# spec/factories.rb
FactoryGirl.define do
factory :link do
title {"Testing Rails"}
url {"http://testingrailsbook.com"}
end
end
# In your test link = create(:link)
# Or override the title link = create(:link, title: "TDD isn't Dead!")
factories 比 fixture 慢一点,但是灵活一些, 值得使用. group :development, :test do gem 'factory_bot_rails' end
还要数据库清理 Instead of using the database_cleaner gem directly, each ORM has its own gem. Most projects will only need the database_cleaner-active_record gem:
# Gemfile
group :test do
gem 'database_cleaner-active_record'
end
If you are using multiple ORMs, just load multiple gems:
# Gemfile
group :test do
gem 'database_cleaner-active_record'
gem 'database_cleaner-redis'
end
Install the new gems and create a new file spec/support/factory_bot.rb : # spec/support/factory_bot.rb RSpec.configure do |config| config.include FactoryBot::Syntax::Methods config.before(:suite) do begin DatabaseCleaner.start FactoryBot.lint ensure DatabaseCleaner.clean end end end 测试套件运行前,这个file will lint your factories . make sure 所有的factories都是valid. FactoryBot.lint会写数据到数据库,所以需要Database Cleaner来还原数据库状态. 现在还需要在你的rails_helper里面包含这个factory. 把支持文件夹下面的文件都引入吧.
# Uncomment me! Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
现在就可以创建 factories file了. #spec/factories.rb
FactoryBot.define do end
#spec/features/user_views_homepage_spec.rb .
require "rails_helper" RSpec.feature "User views homepage" do scenario "they see existing links" do
end end
我们用Factory的create link = create(:link) 但是....有错误, 应该是FactoryBot.create,,, 为了保存代码干净, 我们更好的做法是在Link class里面写这个代码. # spec/factories.rb factory :link do title "Testing Rails" url "http://testingrailsbook.com" end 注意:定义之定义最少的field, 多了后就不可管理了. We only define defaults for fields that we validate presence of. Not following this advice is a common mistake in Rails codebases and leads to major headaches.
测试的时候,通过 data-role来定位页面上的元素,不要通过css,因为css会变. We’ll frequently use data-role s to decouple our test logic from our presentation logic. This way, we can change class names and tags without breaking our tests!
单元测试 RSpec.describe Link, "#upvote", type: :model do
end
We prefix instance methods with a # and class methods with a . 实例方法前面加# 类方法前面加.
link = build(:link, upvotes: 1) .build is another FactoryGirl method. It’s similar to .create , in that it instantiates an object based on our factory definition, however .build does not save the object. .build 方法不保存到对象里. Whenever possible, we’re going to favor .build over .create , as persisting to the database is one of the slowest operations in our tests. 可能的情况下,我们更喜欢用build,而不是create, 比较create要执行比较慢的写入数据库的操作. 那你可能会问: 为什么不用Link.new 呢? 通new创建的对象是不会立即写入对象, 但是一旦你调用方法,那就写入对象了......
比如 link = Link.new(upvotes: 2, downvotes: 1) link.upvote link.upvote Link Update All (0.2ms) UPDATE "links" SET "upvotes" = COALESCE("upvotes", 0) + ? WHERE "links"."id" IS NULL [["upvotes", 11]]
后面的比较零碎, 简要的做些记录
测试是个单独的孤立的组件.但这是理论上的,That's nice in theory. but in the real world most objects depend on collaborators which may in turn depend on their own collaborators. You set out to ()test a single object and end up with a whole sub-system.