ถ้าแอปพลิเคชัน Ruby ของคุณใช้ API ภายนอกใดๆ คุณน่าจะเผชิญปัญหา เทสต์ช้าและขีดจำกัดอัตรา API (API rate limits)
โซลูชันคืออะไร?
คุณอาจจะ stub เมธอด HTTP จากไลบรารี client ด้วยมือ และคืนคำตอบที่กำหนดไว้ล่วงหน้า
แต่มันเป็นงานเยอะและโค้ด醜陋!
โซลูชันที่ดีกว่าคือใช้ การรวมพลังของ gems เช่น Webmock + VCR
WebMock จับคำขอ HTTP จากไลบรารี HTTP ทั่วไป เช่น:
- net/http
- Faraday
- RestClient
- ...อีกเยอะ!
สิ่งนี้มีประโยชน์แล้ว แตคุณยังต้องให้ข้อมูลคำตอบ
นี่คือที่มาของ VCR...
VCR ทำงานร่วมกับ WebMock เพื่อ บันทึกการตอบสนอง HTTP ที่โค้ดของคุณสร้าง
การบันทึกเหล่านี้เรียกว่า "cassettes" (คาสเซ็ต)
เมื่อคุณรันเทสต์:
VCR จะโหลดไฟล์ cassette และคืนคำตอบที่บันทึกไว้ คุณจะได้การตอบสนองเร็วขึ้น เพราะไม่ต้องขอ API จริง
มา看看ตัวอย่างโค้ด!
ตัวอย่างโค้ด VCR
สำหรับตัวอย่างนี้ เราจะใช้ RSpec เพราะมัน integrate กับ VCR ได้ดีกว่า ตามที่คุณจะเห็นในส่วนต่อไป
โค้ดที่เราต้องการทดสอบ:
require "faraday"
require "json"
class Github
def self.user(name)
url = "https://api.github.com/users/#{name}"
data = Faraday.get(url).body
JSON.parse(data, symbolize_names: true)
end
end
มันทำคำขอไปที่ Github API เพื่อรับข้อมูลผู้ใช้เฉพาะ ค่อนข้างเรียบง่าย แตจะช่วยเราเรียนรู้ VCR
เทสต์สำหรับโค้ดนี้อาจเป็นแบบนี้:
require "rspec/autorun"
require_relative "github_api_example"
describe Github do
let(:user_response) { Github.user("ruby") }
it "can fetch & parse user data" do
expect(user_response).to be_kind_of(Hash)
expect(user_response).to have_key(:id)
expect(user_response).to have_key(:type)
end
end
นี่จะเรียก API จริง และผ่าน แตใช้เวลาประมาณครึ่งวินาที
ครึ่งวินาทีสำหรับ หนึ่งเทสต์!
อาจดูไม่มาก แต่วิ่งลองนึกภาพว่าคุณมี 100 เทสต์ นั่นคือ 50 วินาทีในการรันทั้งหมด
เวลาแก้ไขแล้ว...
มาเพิ่ม VCR ด้วยโค้ดนี้:
require "vcr" VCR.configure do |c| c.cassette_library_dir = "spec/vcr" c.hook_into :webmock end
คุณสามารถเพิ่มโค้ดนี้ในไฟล์
test_helperเพื่อให้พร้อมใช้สำหรับเทสต์ทั้งหมด
ด้วยบล็อก configure นี้ คุณบอก VCR ว่าจะบันทึกไฟล์ cassette ที่ไหน และเปิดใช้งาน WebMock integration
ถ้าคุณใช้ Faraday หรือ Excon VCR สามารถ hook เข้าไปได้โดยตรง
แค่แทน :webmock ด้วย :faraday หรือ :excon
ต่อไป:
คุณต้องบอก VCR ชื่อ cassette และโค้ดไหนควรทำงานภายใต้ cassette นั้น
วิธีทำ:
let(:user_response) do
VCR.use_cassette("github/user") { Github.user("ruby") }
end
เมื่อคุณรันเทสต์ VCR จะสร้างไฟล์ภายใต้ cassette_library_dir ในกรณีนี้ชื่อไฟล์จะเป็น spec/vcr/github/user.yaml หากคุณติดตามมาอยู่ คุณอาจอยากดูมัน
ถ้าผมรันเทสต์ตอนนี้ มันจะ เร็วขึ้นมาก
จริงๆ แล้ว...
มันใช้แค่ 0.01 วินาที!
"An HTTP request has been made that VCR does not know how to handle"
หากคุณเจอข้อความข้อผิดพลาดนี้ หมายถึงหนึ่งในสองอย่าง:
1. คุณพยายามทำคำขอ HTTP พร้อม VCR เปิดอยู่ แต่นอกบล็อก VCR.use_cassette
วิธีแก้: โดยค่าเริ่มต้น VCR + WebMock บล็อกคำขอ HTTP ทั้งหมด คุณสามารถเปลี่ยนได้ด้วยตัวเลือกการตั้งค่า เพิ่มบล็อก VCR ที่ขาดหายไป หรือใช้ RSpec metadata (ส่วนต่อไป)
2. คุณพยายามทำคำขอที่ต่างออกไป และใช้ cassette ที่ไม่ตรงกับ URL นี้ ตัวอย่าง หากคุณ request ในเทสต์ /users/ruby cassette ถูกสร้างเฉพาะสำหรับ URL นี้ หากคุณเปลี่ยนเทสต์เป็น /users/apple คุณจะเห็นข้อผิดพลาดนี้ เพราะ cassette เป็นสำหรับ URL อื่น
วิธีแก้: ใช้ cassette ต่างกันสำหรับ URL ต่างกัน เปิดโหมด recording new_episodes (vcr: { record: :new_episodes }) หรือลบ cassette เก่าหลังอัปเดต URL request
วิธีใช้ RSpec Metadata
เมธอด VCR.use_cassette เป็นวิธีที่ดีในการใช้ gem นี้
แต่...
คุณยังสามารถให้ VCR สร้าง cassettes ให้อัตโนมัติได้
อย่างไร?
เพิ่มนี้ภายในบล็อก VCR.configure:
c.configure_rspec_metadata!
ตอนนี้คุณสามารถเปิดใช้งาน VCR สำหรับเทสต์เฉพาะ (it block) หรือกลุ่มเทสต์ (describe)
เช่นนี้:
describe Github, :vcr do # ... end
สิ่งนี้จะสร้างไฟล์ cassette ชื่อตาม คำอธิบายของเทสต์:
spec/vcr/
└── Github
├── can_parse_user_data.yml
└── can_test_vcr.yml
สังเกตว่านี่จะสร้าง cassette ต่อเทสต์...
แม้สองเทสต์จะทำคำขอเดียวกันและใช้ข้อมูลเดียวกัน VCR จะสร้าง cassette แยกสำหรับแต่ละอัน
ตัวเลือกและเคล็ดลับ VCR ที่มีประโยชน์
หากคุณมีปัญหากับ cassettes หรืออยากข้อมูลรุ่นใหม่ คุณสามารถ ลบไฟล์ cassette
VCR จะบันทึกคำตอบ API ใหม่ และอาจแก้ปัญหาของคุณ
แตถ้ามันไม่ได้?
คุณสามารถเปิด debug mode ใน VCR.configure:
VCR.configure do |c| # ... c.debug_logger = $stderr end
สิ่งนี้อาจให้เอาต์พุทเยอะ จำกัดเทสต์เฉพาะที่คุณสนใจ
ต่อไป:
สมมติว่ามี API keys หรือข้อมูลละเอียดอ่อนอื่นเป็นส่วนหนึ่งของคำตอบ API...
คุณอาจอยากกรองข้อมูลนั้นออกจากการบันทึก
วิธีทำ:
VCR.configure do |c|
# ...
c.define_cassette_placeholder("<API_KEY>", ENV["API_KEY"])
end
สรุป
คุณได้เรียนรู้วิธีใช้ gems WebMock และ VCR เพื่อเขียนเทสต์สำหรับแอป Ruby โดยไม่ต้องรอคำตอบจาก API ภายนอก!

ถ้าชอบ อย่าลืมแชร์บทความนี้เพื่อให้คนอื่นเพลิดเพลินไปกับมัน
ขอบคุณที่อ่าน 🙂