该文章同步发布於:我的部落格
WebMock 以及 VCR 是拿来实作关於网站请求的工具,在这篇文章中也会解释两者分别是什麽,并且做一个简单的教学!
测试的原则之一就是测试的确定性,一个测试的通过以及失败都应该是应用程序的程序码内容来决定,而不是其他的因素,所有的测试都理应要通过,无论他是用什麽方式运行,在什麽时间运行,或是其他的因素。
由於这个原因,我们就必须在封装的世界中运行测试,如果我们想要测试的确定性以及稳定性,我们就不该让测试和网路对话,因为网路有很多的变因可能改变测试的结果。
想像一下程序里面和第三方 API 的交流,在想像一下每次的测试都会去触碰这个 API,在想像一下,某一次的测试运行中,第三方的 API 碰巧坏掉了,导致测试的失败,但这次的失败明显是不合逻辑的,因为测试要做的目的是要告诉我们的程序码有问题,但这次不是,是外部的干扰导致而成!
如果我们的测试不想和真实的网路对话,那我们就需要一种方法来模拟和网路的互动,让我们的应用程序在一个稳定的环境中进行测试,这就是 VCR & WebMock 为什麽会存在的原因!
我们也不希望测试的过程中,真实的对资料有任何的变动,假设我们写了一个删除用户的测试,总不希望测试的过程中真的影响了资料吧?因此,VCR & WebMock 也给了我们不必接触真实资料的功能!
VCR 是一个拿来纪录你的应用程序和 HTTP 交流过程的工具,并且在之後进行回放,而且不需要写什麽程序码,VCR 欺骗你的应用程序,让他以为他正在接受来自网路的回覆,但实际上你的专案只是在接受预录的 VCR 数据而已!
WebMock 则不像 VCR 那样可以纪录和回放,尽管 HTTP 的交流是可以被伪造的,但那和 VCR 还是有所不同,WebMock 需要写更多的程序码,而且也有诸多的限制,在这次的文章中,会有很基本的介绍和了解!
这篇文章将会做一个简单的例子,说明如何使用 VCR & WebMock 来满足需求,执行对於网路的请求,但不实际的发送请求出去!
至於这篇文章的基础 Rails Setup 就不展示了,主要专注在工具的使用上面!
情境
我们要写一个小型的搜寻功能,去请求第三方的 API,所以在测试中,我们也该发送出 API 来完成测试
WebMock
一但测试完成的差不多了,我们就会安装和设定 WebMock,这时候会禁止任何的网路请求,因此,我们的测试会停止运作。
VCR
最後,我们会安装和设定 VCR,VCR 能够和 WebMock 做互动,也因为这样,VCR & WebMock 可以一起合作,在某些条件下,我们的测试可以进入网路,VCR 将纪录测试过程的 HTTP 请求及回覆,接着在之後的测试中,都会使用 VCR 的回放,而不是运作都发送新的请求!
因为临时想不到有什麽第三方的资源可以去打,就用想像的方式来进行~
今天这篇文章实作的功能是一个搜寻某些第三方资料的功能,使用者可以输入姓名,点击搜寻,然後会有资料出现!
下面是 Controller 的程序码:
# app/controllers/data_searches_controller.rb
ENDPOINT_URL = "https://xxxx/api"
class DataSearchesController < ApplicationController
def new
@results = []
return unless params[:first_name].present? || params[:last_name].present?
query_string = {
first_name: params[:first_name],
last_name: params[:last_name]
}.to_query
uri = URI("#{ENDPOINT_URL}/?#{query_string}")
response = Net::HTTP.get_response(uri)
@results = JSON.parse(response.body)["results"]
end
end
我们也应该要有 View 来 render 画面:
<%= form_tag xxx_search_path, method: :get do %>
<%= text_field_tag :first_name, params[:first_name] %>
<%= text_field_tag :last_name, params[:last_name] %>
<%= submit_tag "搜寻" %>
<% end %>
<% @results.each do |result| %>
<div>
<%= result["basic"]["first_name"] %>
<%= result["basic"]["last_name"] %>
<%= result["number"] %>
</div>
<% end %>
简单的写一个 feature test,在姓名的栏位分别填入 "Robert" & "Chang",点击搜寻,然後期待 Robert Chang 的某个识别证号码会出现在画面上!
# spec/feature/data_search_spec.rb
require "rails_helper"
RSpec.feature "Data Search", type: :feature do
scenario "show the identify number" do
visit data_search_path
fill_in "first_name", with: "Robert"
fill_in "last_name", with: "Chang"
click_on "搜寻"
# 这段是 RobertChang 的识别码
expect(page).to have_content("118583921")
end
end
OK,这样会通过,没有问题!
像刚刚前面提过的,我们不希望测试时也真的送出第三方的 API 请求,所以我们需要假造一段请求。
我们先把 WebMock 装入 Gemfile:
group :development, :test do
gem 'webmock', '~> 3.14'
end
第二步,我们建立一个档案在 spec/support/webmock.rb
,并且加入下面的程序码:
# spec/support/webmock.rb
# This line makes it so WebMock and RSpec know how to talk to each other.
require "webmock/rspec"
# This line disables HTTP requests, with the exception of HTTP requests
# to localhost.
WebMock.disable_net_connect!(allow_localhost: true)
记得到 spec/rails_helper.rb
里面去 uncomment 下面那一行,不然 spec/support
的文件不会被 Load 进来用。
# spec/rails_helper.rb
# Make sure to uncomment this line
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
第三步:看测试坏掉!
安装之後再跑了一次测试,他会坏掉,然後说 Real HTTP connections are disabled
,还给我们一些提示,叫我们可以去 Stub 这个请求,但我们不这麽做,我们要用 VCR 来替代!
Failures:
1) Data Search show the indentify number
Failure/Error: response = Net::HTTP.get_response(uri)
WebMock::NetConnectNotAllowedError:
Real HTTP connections are disabled. Unregistered request: GET https://xxx/api/?first_name=Robert&last_name=Chang with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Host'=>'xxx', 'User-Agent'=>'Ruby'}
You can stub this request with the following snippet:
stub_request(:get, "https://xxx/api/?first_name=Robert&last_name=Chang").
with(
headers: {
'Accept'=>'*/*',
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'Host'=>'xxx',
'User-Agent'=>'Ruby'
}).
to_return(status: 200, body: "", headers: {})
============================================================
一样先加入 Gemfile:
# Gemfile
group :development, :test do
gem 'vcr', '~> 6.0'
end
接着我会加入这个设定档,我有加入了自己的注解,这样可以更快理解!
# spec/support/vcr.rb
VCR.configure do |c|
# 这个资料夹是存放 VCR 的地方,就是存放和 HTTP 纪录的过程
c.cassette_library_dir = "spec/cassettes"
# 这行让 VCR 和 WebMock 知道如何和彼此沟通!
c.hook_into :webmock
# 这行让 VCR 忽略了对 localhost 的请求,这是必要的尽管 WebMock allow_localhost 是 true
c.ignore_localhost = true
# ChromeDriver 会向 chromedriver.storage.googleapis.com 发送 update 的请求。
# 这些请求会在我们的 cassette 中发出噪音,除非我们告诉 VCR 去忽略他
c.ignore_hosts "chromedriver.storage.googleapis.com"
end
加入 VCR 罗!
现在我们可以加入 VCR.use_cassette "data_search"
的 block 进入测试中,data_search
是可以任意取的名字,只是让 VCR 可以辨识。
# spec/feature/data_search_spec.rb
require "rails_helper"
RSpec.feature "Data Search", type: :feature do
scenario "show the identify number" do
VCR.use_cassette "data_search" do # <----- 加这行
visit data_search_path
fill_in "first_name", with: "Robert"
fill_in "last_name", with: "Chang"
click_on "搜寻"
# 这段是 RobertChang 的识别码
expect(page).to have_content("118583921")
end
end
end
上次在只有安装 WebMock 的时候喷错了,因为 WebMock 阻止了 HTTP 的请求,但现在执行这个测试会通过,因为 VCR 和 WebMock 一起让 HTTP 请求发生。
结束会去看看 spec/cassettes
资料夹,会发现有一个叫做 data_search.yml
的档案,内容如下:
---
http_interactions:
- request:
method: get
uri: https://xxx/api/?first_name=Robert&last_name=Chang
body:
encoding: US-ASCII
string: ''
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
Host:
- xxx
response:
status:
code: 200
message: OK
headers:
Date:
- Fri, 18 Oct 2021 03:02:41 GMT
Content-Type:
- application/json
Strict-Transport-Security:
- max-age=31536000; includeSubDomains
Set-Cookie:
- TS017b4e40=01acfeb9489bd3c233ef0e8a55b458849e619bdc886c02193c4772ba662379fa1f8493887950c06233f28bbbaac373afba8b58b00f;
Path=/; Domain=xxx
Transfer-Encoding:
- chunked
body:
encoding: UTF-8
string: '{ 回传的资料... }'
recorded_at: Fri, 18 Oct 2021 03:02:41 GMT
recorded_with: VCR 6.0.0
接着在之後的每次测试,VCR 都会问,有没有一个叫做 data_search
的录影带,没有的话就允许去执行一次 HTTP 的请求,有的话就使用它!
今天花了蛮多的时间写这个的,内容都是根据之前自己玩的 sideproject 改编内容而成~
所以 VCR 中的回传内容应该会是一些 json 格式的资料,但我相信理解概念就可以了,和实际运作的 Code 没有太大的关系!
感谢收看~
<<: DAY24 - 利用 uptime 让你的 Heroku 永不休眠
>>: Day 0x 1D - odoo addons 永丰金流开发(Part 4 - Website template, data... more)
2021版本才能存云端文件 => 可以版本控制 =>视窗 =>版本纪录 1.基础...
晚上在看线图的时候,发现铼德2349有几个观点可以提出来跟大家分享。 在今年1月初时,跌破季线,这时...
这个得上一篇:https://ithelp.ithome.com.tw/articles/10258...
#来抽Airpods Pro, Marshall喇叭, 空气清净机, 全联/7-11/Starbuc...
Handler类别 使不同Thread沟通的机制,将Thread要传递的讯息放入Message类别,...