我经常被问到,怎样开始测试 Rails 程序。其实最为一个测试新手,最可贵地方在于你不知道一些专业术语或者该问怎样的问题。下面所写的是一些概览,关于咱们使用什么工具,为何使用这些工具,和一些须要牢记在心的建议。css
咱们用 RSpec 而不是 Test::Unit,是由于语法更友好,可读性更高。固然,你能够花几天时间争论到底该用哪一个框架,每一个框架都有本身的优势。最主要的是你在用他们进行测试。html
Feature specs,能够测试你整个程序的高级测试工具,保证每一个部件都工做正常,挺赞的。他是从用户的角度编写的,好比用户点击或者填写表单。咱们用 RSpec 和 Capybara,他们容许你写照这样编写能够和网页进行交互的测试。git
这是一个 RSpec feature 测试的栗子:github
# spec/features/user_creates_a_foobar_spec.rb feature 'User creates a foobar' do scenario 'they see the foobar on the page' do visit new_foobar_path fill_in 'Name', with: 'My foobar' click_button 'Create Foobar' expect(page).to have_css '.foobar-name', 'My foobar' end end
这个测试,模拟了一个用户打开新建foobar
的表单,填入信息,点击“Create”。这个测试而后假设页面正如期待,正确地显示刚才建立的foobar
。web
这些对于测试高级功能来讲很棒,可是记住,feature specs 跑起来很慢。在用 Capybara 测试应用全部的可能路径的时候,把测试的边缘状况留模型,视图,控制器的 sepecs。数据库
我倾向于关于怎么区分 Rspec 和 Capybara 方法的不一样点。Capybara 方法其实是和页面进行交互,好比点击,表单操做,或者在页面上查找元素。你能够在 Capybara 的finders,matchers,和 actions 查看更多文档。浏览器
Model specs 常常被用来测试系统中较小的部件,好比类或方法,这点和单元测试挺类似的。有时,他们也会和数据库进行交互。他们运行起来很快,也能够处理正在测试系统(system under test)中的一些边缘状况。ruby
在 RSpec 中,他们看起来像这样:网络
# spec/models/user_spec.rb # Prefix class methods with a '.' describe User, '.active' do it 'returns only active users' do # setup active_user = create(:user, active: true) non_active_user = create(:user, active: false) # exercise result = User.active # verify expect(result).to eq [active_user] # teardown is handled for you by RSpec end end # Prefix instance methods with a '#' describe User, '#name' do it 'returns the concatenated first and last name' do # setup user = build(:user, first_name: 'Josh', last_name: 'Steiner') # excercise and verify expect(user.name).to eq 'Josh Steiner' end end
为了维护的可读性,确保你写的测试是 Four Phase Testsession
The Four-Phase Test is a testing pattern, applicable to all programming languages and unit tests (not so much integration tests).
It takes the following general form:
test do setup exercise verify teardown end
好比:
it 'encrypts the password' do user = User.new(password: 'password') user.save user.encrypted_password.should_not be_nil end
当经过一个控制器测试多个路径的时候,咱们喜欢用 controller specs 而不是 feature specs,由于他很快,并且写起来容易。
一个好的测试验证的 use case:
# spec/controllers/sessions_controller_spec.rb describe 'POST #create' do context 'when password is invalid' do it 'renders the page with error' do user = create(:user) post :create, session: { email: user.email, password: 'invalid' } expect(response).to render_template(:new) expect(flash[:notice]).to match(/^Email and password do not match/) end end context 'when password is valid' do it 'sets the user in the session and redirects them to their dashboard' do user = create(:user) post :create, session: { email: user.email, password: user.password } expect(response).to redirect_to '/dashboard' expect(controller.current_user).to eq user end end end
View specs 对于测试在模板中根据条件显示不一样信息来讲很是有用,可是不少开发者却忘了这个,而用 feature specs。而后绞尽脑汁为何他们花了那么长时间跑测试。固然,你能够用 feature spec 覆盖每个条件视图,但我更喜欢像这样使用 view specs :
# spec/views/products/_product.html.erb_spec.rb describe 'products/_product.html.erb' do context 'when the product has a url' do it 'displays the url' do assign(:product, build(:product, url: 'http://example.com') render expect(rendered).to have_link 'Product', href: 'http://example.com' end end context 'when the product url is nil' do it "displays 'None'" do assign(:product, build(:product, url: nil) render expect(rendered).to have_content 'None' end end end
当编写测试代码的时候,你会须要在不一样场景往数据库里灌数据。你可使用内建的User.create
,可是当 model 中有不少 validations 时候,这样好乏味啊。经过User.create
,即使你的测试代码和这些验证无关,你也不得不指定属性来知足 validations。最重要的是,若是后来你修改了 validations,你还要从新修改测试套件的代码。解决方案就是用 factories(工厂,数据生成器) 或者 fixtures(夹具)来建立模型。
咱们更喜欢 factories(和 FactoryGirl)赛过 Rails 的 fixgures,由于 fixtures 是神秘嘉宾。夹具让人很难看到内因和效果,由于部分逻辑在你使用它的时候已经在文件中被定义好了。由于夹具远在在测试以前就已经被生成,他们变得很难控制。
Factories,另外一方面来讲,把逻辑正确地放到测试中。这让咱们很容易地看到正在发生什么,并且对于不一样的场景更加灵活。Factories 比夹具更慢,可是从灵活性和可读性上来讲,这点牺牲是值得的!
把数据固化到数据库也会减慢测试速度。不管合适,优先使用 FactoryGirl 的 build_stubbed
,而不是create
。build_stubbed
会在内存中生成,避免写入磁盘。若是你测试一些查询操做(User.where(admin: true)
),你会但愿从数据库里进行查找,这就意味着你必须使用create
。
你会最终碰到一种场景,你须要测试一些依赖 JavaScript 代码的功能。用默认的驱动跑 specs 不会执行页面中任何 JavaScript 代码。
你须要两个法器,来跑带有 JavaScript 代码的功能 specs
有两种类型的 JavaScript 驱动。好比像 Selenium,它会打开一个 GUI 窗口浏览器,而后在你看着它的时候,在页面上点击。这是一个颇有用的工具在测试中用来视图化。可是不幸的是,启动一整个 GUI 窗口浏览器很慢。因为这个缘由,咱们倾向于使用 headless 浏览器。对于 Rails 来讲,你可能会使用 Poltergeist 或者 [Capybara] Webkit(https://github.com/thoughtbot/capybara-webkit)。
feature 'User creates a foobar' do scenario 'they see the foobar on the page', js: true do ... end end
用合适的关键字,RSpec 会根据须要运行任何 JavaScript。
默认状况下,跑 Rails 测试的时候,Rails 会把每一个场景都存在数据库事务中。这就说明,在每一个测试结束的时候,Rails 会回滚全部在测试中的修改。这点很是好,由于咱们不想任何测试数据会对别的测试产生反作用。
不幸的是,当咱们使用 JavaScript 驱动的时候,这个测试实在另外一个线程进行的。这就意味着,它并无和程序共用同一个数据库链接,并且为了运行程序看到测试结果,你的测试会提交这个事务。为了解决这个问题,咱们能够容许数据库提交这些数据,而后接着,在每一个 spec 后 Truncate
数据库。这样会比事务慢一点,因此,咱们只在须要的时候使用 truncation
。
这就是 Database Cleaner 的用处。Database Cleaner 容许你配置策略。我建议阅读下 Avdi 的文章 看看血淋淋的细节。这是个极(丧)其(心)详(病)细(狂)的配置过程,因此我一般在项目中来回拷贝这个文件,或者使用 Suspenders,这样就能够轻易搞定了。
double 方法能够模拟系统中另一个对象。常常的,你会须要一个替身,而且只测试一个属性,因此不值得加载整个 ActiveRecord 对象。
car = double(:car)
当你使用 stubs,你是在告诉一个对象去响应一个已给出的方法。若是 stub 以前的 double
car.stub(:max_speed).and_return(120)
咱们如今能够期待当访问 max_speed
咱们的 car
对象老是返回120
。临时产生能够响应一个方法的对象并且带有一样的依赖关系,而不用系统中真的存在的对象,这是很棒的方法!在这个栗子中,咱们在一个 double 出来的对象上进行了 stub,实际上你还能够在别的对象 stub 任何方法。
咱们能够把上边的代码简化为这样:
car = double(:car, max_spped: 120)
当测试你的程序时,你会碰到你想验证当一个对象接收到一个指定的方法的场景。为了遵守 Four Phase Test 的最佳实践,咱们用测试间谍,这样咱们的期待会进入最佳的 verify
状态。以前,咱们用 Bourne 作到这点,可是 RSpec 已经在 RSpec Mocks 中包括了这项功能。看看文档中的这个栗子:
invitation = double('invitation', accept: true) user.accept_invitation(invitation) expect(invitation).to have_received(:accept)
基于三方服务的测试套件跑起来很慢的,会由于网络链接的断开致使失败,并且也可能会由于服务频率限制或者缺乏沙盒环境致使失败。
确保你的测试套件在用 Webmock stub 外部请求的时候,不会和三方服务交互。这个能够配置在 spec/spec_helper.rb
:
require 'webmock/rspec' WebMock.disable_net_connect!(allow_localhost: true)
避免使用三方请求,学习怎样 stub 外部服务请求。
这仅仅是个如何开始测试 Rails 应用的概览。为了促进学习,我很是推荐你上咱们的 TDD workshop,在这里,你能够经过从零开始创建两个 Rails 应用,来深度学这些课程。课程覆盖到重构,为确保应用和测试代码的可维护性。TDDworkshop 的学生也能够介入咱们的工做时间,你实时地能够问咱们攻城狮任何问题。
当我是新手的时候我学习了这个课程,我墙裂推荐!