การทดสอบเป็นส่วนสำคัญของแอปพลิเคชัน Rails ที่ทำงานได้ดี โดยที่การบำรุงรักษาไม่ใช่ฝันร้าย และฟีเจอร์ใหม่ถูกเพิ่มเข้ามาอย่างต่อเนื่อง หรือฟีเจอร์เดิมได้รับการปรับปรุง อย่างน่าเสียดาย สำหรับแอปพลิเคชันหลายตัว สภาพแวดล้อม Production คือสถานที่แรกที่พวกเขาถูกทดสอบภายใต้ภาระงานหนักหรือトラフィックมากครั้งแรก เรื่องนี้เข้าใจได้ เพราะการทดสอบประเภทนี้มีค่าใช้จ่ายสูง
ขอขอบคุณที่ Rails มีการสนับสนุนที่ดีไม่เพียงแค่ Unit, End-to-End และ Integration Tests แต่ยังรวมถึงการทดสอบที่เกี่ยวกับประสิทธิภาพและโหลดด้วย ฉันจะครอบคลุมทั้งหมดในบทความนี้และแสดงตัวอย่างปฏิบัติที่จะช่วยให้คุณเข้าใจวิธีใช้เครื่องมือทดสอบระดับประสิทธิภาพของแอปพลิเคชันอย่างมีประสิทธิภาพ
บทความแบ่งออกเป็น 2 ส่วน:
ทฤษฎี — ฉันจะแสดงว่าทำไมการทดสอบจึงจำเป็น ประเภทของการทดสอบที่เราสามารถทำได้ และ Metric สำคัญเมื่อทดสอบแอปพลิเคชัน
ปฏิบัติ — เราจะลงมือจริงและเขียนการทดสอบสำหรับแอปพลิเคชันจริงเพื่อได้ผลลัพธ์
หลังจากอ่าน 2 ส่วน คุณจะมีความเข้าใจลึกซึ้งเกี่ยวกับประเภทการทดสอบต่างๆ และวิธีการทดสอบบนแอปพลิเคชัน Rails ของคุณ ดูน่าสนใจไหม? มาเริ่มต้นด้วยทฤษฎีสั้นๆ เกี่ยวกับการทดสอบกัน
ทฤษฎีการทดสอบ
การทดสอบควรเป็นส่วนจำเป็นของการพัฒนาแอปพลิเคชันทุกประเภท หากคุณยังไม่เชื่อเรื่องนั้น หรือยังไม่เคยเขียนการทดสอบเลย นี่คือข้อโต้แย้งสำหรับการทดสอบที่จะช่วยคุณ:
- นำการเปลี่ยนแปลงมาใช้โดยไม่กังวลว่าจะทำให้สิ่ง gìเสีย — นี่คือเหตุผลหลักที่การทดสอบจำเป็น ทำให้จินตนาการทำงานบนแอปขนาดใหญ่ที่คุณต้องคลิกผ่านทั้งแอปเพื่อให้แน่ใจว่ nichts เสียทุกครั้งที่นำการเปลี่ยนแปลงมาแม้แต่วิธีเล็กๆ ด้วยการทดสอบ คุณเพียงรันคำสั่งเดียวและกระบวนการตรวจสอบจะเป็นอัตโนมัติและรวดเร็ว
- กระบวนการ Refactoring ที่ง่าย — ฉันกล่าวไปข้างต้นว่าการทดสอบจำเป็นเมื่อเพิ่มฟีเจอร์ใหม่หรือทำการเปลี่ยนแปลง ด้วยการทดสอบที่มีอยู่ คุณจะมั่นใจมากขึ้นในการปรับปรุงโค้ดที่มีอยู่
- การทดสอบเป็นรูปแบบหนึ่งของเอกสาร — การทดสอบที่เขียนได้ดีสามารถเป็นเอกสารสำหรับชุดฟีเจอร์ต่างๆ ในแอปพลิเคชัน พวกเขาไม่ได้แค่อธิบายว่าฟีเจอร์คืออะไร แต่ยังอธิบายว่าควรทำงานอย่างไร
- โอกาสในการคิดใหม่เกี่ยวกับการ Implement — เมื่อคุณเขียนการทดสอบ คุณมีโอกาสคิดอีกครั้งว่าวิธีที่คุณต้องการ Implement โค้ดถูกต้องและสมเหตุสมผลหรือไม่ นอกจากนี้ คุณเพียงแค่ตรวจสอบว่าโค้ดของคุณทำงานตามที่คุณคาดหวัง
หวังว่าข้อโต้แย้งข้างต้นจะชักจูงให้คุณใช้การทดสอบระหว่างการพัฒนาแอปใดๆ ในขณะที่การรู้ว่าทำไมต้องทดสอบโค้ดเป็นสิ่งสำคัญ การเรียนรู้เกี่ยวกับประเภทการทดสอบที่แตกต่างกันก็สำคัญเช่นกัน
ประเภทการทดสอบที่แตกต่างกัน
มีการทดสอบหลัก 3 ประเภทที่คุณสามารถเขียนเพื่อให้แน่ใจว่าประสิทธิภาพของแอปพลิเคชัน Rails ถูกต้อง และโครงสร้างพื้นฐานทำงานได้ดีภายใต้ภาระงานหนัก ประเภทเหล่านั้นคือ:
- Load Testing — ประเภทการทดสอบนี้ตอบคำถามว่า: ระบบสามารถจัดการผู้ใช้พร้อมกันได้กี่คนในช่วงเวลาที่กำหนด ทำให้จินตนาการเปิดตัวผลิตภัณฑ์ยอดนิยมบนเว็บไซต์ และพันๆ ผู้ใช้ต้องการสั่งซื้อพร้อมกัน โดยไม่มีการทดสอบโหลดที่เหมาะสม คุณเสี่ยงต่อการ Crash ในช่วงเวลาสำคัญที่สุด
- Stress Testing — ด้วยประเภทการทดสอบนี้ คุณไม่เน้นการตรวจสอบจำนวนผู้ใช้ที่ระบบจัดการพร้อมกันได้ แต่เน้นว่าระบบจะพฤติกรรมอย่างไรเมื่อถึงขีดจำกัดของผู้ใช้
- Performance Testing — ฉันจะบอกว่าประเภทการทดสอบนี้เป็นพ่อแม่ของ Stress และ Load Testing วัตถุประสงค์หลักคือการได้ชุด Metric เฉพาะที่เราสามารถนำไปปรับปรุงโค้ดแอปพลิเคชัน ฉันจะพูดถึง Metric เหล่านี้ในไม่ช้า
สิ่งนั้นกล่าวไปแล้ว เราพร้อมที่จะไปขั้นตอนสุดท้ายของส่วนทฤษฎี: เรียนรู้ว่า Metric อะไรสำคัญเมื่อทำ Performance Testing บนแอปพลิเคชัน Rails โดยไม่มีความรู้นั้น เราจะไม่สามารถตีความ Output ของการทดสอบได้อย่างถูกต้อง และตัดสินใจว่าเราควรเปลี่ยนโค้ดหรือไม่
Metric สำคัญ
ประเภทของ Metric ที่คุณได้รับอาจแตกต่างกันไปขึ้นอยู่กับเครื่องมือที่คุณใช้สำหรับการทดสอบ แต่โดยทั่วไปเราสามารถจัดกลุ่มได้เป็นชุด Metric ที่ค่อนข้างพบได้บ่อย:
- Response Time — เวลาระหว่างการ Request ถูกส่ง และ Response ถูก Render ใน Browser Metric นี้แสดงว่าผู้ใช้ต้องรอนานแค่ไหนก่อนจะได้รับข้อมูลที่เขาร้องขอ บางครั้งเรียกว่า Process Time
- Memory Usage — ปริมาณหน่วยความจำที่ใช้สำหรับ Request ที่กำหนด นี่เป็นข้อมูลสำคัญเพราะมันชี้ไปที่จุดที่คุณสามารถปรับปรุงโค้ดได้ เพื่อให้ระบบตอบสนองเร็วขึ้นและใช้น้อยทรัพยากรน้อยลง
- Objects Allocation — การจัดสรรหน่วยความจำสูงทำให้ Memory Usage สูงและ Response Time ยาว Metric นี้สามารถนำคุณไปยังจุดที่แน่นอนในโค้ดที่มีการจัดสรร Object มาก ที่คุณสามารถตรวจสอบได้ทันที
คุณอาจมี Metric มากกว่านี้เมื่อทดสอบ แต่วันที่ 3 นี้เป็นสำคัญที่สุดและจะถูกต้องสำหรับแอปพลิเคชันใดๆ ที่คุณทดสอบ เราสามารถลงมือจริงและเขียนการทดสอบจริงได้แล้ว
ปฏิบัติ
เราไม่สามารถเขียนการทดสอบได้โดยไม่มีสิ่งที่จะทดสอบ นั่นคือเหตุผลที่ขั้นตอนแรกในส่วนปฏิบัติคือการเขียนแอปพลิเคชัน Rails 簡單ที่เราสามารถเขียนการทดสอบได้
แอปพลิเคชัน Rails ตัวอย่าง
ฉันจะใช้ Ruby 3.0.1 และ Rails 6.1.3.1 แต่คุณสามารถใช้เวอร์ชันที่คุณสบายใจได้ หากคุณติดตั้ง Ruby และ Rails แล้ว ขั้นตอนต่อไปคือสร้างโครงร่างแอปพลิเคชัน:
rails new simpleapp -d=postgresql
เพื่อวัตถุประสงค์ของบทความ ฉันจะสร้างแอปที่นำเสนอรายชื่อผู้ใช้พร้อมชื่อสัตว์เลี้ยง โครงสร้างแบบนี้จะช่วยให้เราสร้าง N+1 Queries ได้ง่าย ซึ่งจะเพิ่มความสนุกเมื่อทำ Performance Tests และตรวจสอบผลกระทบต่อความเร็วและ Metric อื่นๆ จากการเปลี่ยนแปลง
ก่อนที่เราจะสร้าง Model มาสร้าง Database ก่อน:
cd simpleapp/ bin/rails db:create
ตอนนี้เราสามารถสร้าง Model ได้:
rails g model User name:string rails g model Animal name:string user:references bin/rails db:migrate
เพียงแค่ Update เล็กน้อยใน Model User เพื่อสะท้อนความสัมพันธ์กับ Model Animal:
class User < ApplicationRecord has_many :animals end
เราสามารถเพิ่ม Seeds ในไฟล์ db/seeds.rb ได้แล้ว:
people = { 'Tim' => ['Pinky', 'Rick'], 'Martha' => ['Rudolph'], 'Mark' => ['Niki', 'Miki', 'Bella'], 'Tina' => ['Tom', 'Luna'] } people.each_pair do |name, pets| user = User.create(name: name) pets.each do |pet_name| user.animals.create(name: pet_name) end end
และโหลดข้อมูลลง Database:
bin/rails db:seed
ฉันจะสร้าง Controller หนึ่งตัวพร้อมการกำหนด User และใน View จะแสดงรายชื่อ User ทั้งหมดพร้อมชื่อสัตว์เลี้ยง ฉันใช้โค้ดที่ทำให้เกิดปัญหาประสิทธิภาพอย่างเจตนา เพื่อให้คุณวัดการปรับปรุงภายหลัง
touch app/controllers/home_controller.rb mkdir app/views/home touch app/views/home/index.html.erb
Controller เรียบง่าย:
class HomeController < ApplicationController def index @users = User.all end end
และ View ก็เรียบง่ายเช่นกัน:
<h1>List</h1> <ul> <% @users.each do |user| %> <li><%= user.name %> (<%= user.animals.count %>) <ul> <% user.animals.each do |animal| %> <li><%= animal.name %></li> <% end %> </ul> </li> <% end %> </ul>
ขั้นตอนสุดท้ายคือ Update ไฟล์ config/routes.rb ให้ Rails รู้ว่าคุณต้องการเห็นอะไรเมื่อเข้าชม URL หลัก:
Rails.application.routes.draw do root to: 'home#index' end
Load Tests ด้วย JMeter
JMeter เป็นซอฟต์แวร์ Open Source ที่สร้างโดย Apache Software Foundation ออกแบบมาเพื่อ Load Test พฤติกรรมการทำงาน เนื่องจากเป็นโปรแกรมที่สร้างด้วย Java คุณสามารถติดตั้งบน OS ใดๆ ได้ คุณสามารถดาวน์โหลดไฟล์ได้ที่: https://jmeter.apache.org/download_jmeter.cgi
หากคุณทำงานบน macOS คุณสามารถติดตั้ง JMeter ได้ง่ายด้วย Homebrew:
brew install jmeter
หลังติดตั้ง คุณสามารถรันโปรแกรมได้ด้วยคำสั่ง:
jmeter
การกำหนดค่าการทดสอบ
กระบวนการกำหนดค่าประกอบด้วยขั้นตอนต่อไปนี้:
- เพิ่ม Thread Group — ระบุจำนวนผู้ใช้และระยะเวลาที่แต่ละคนจะเข้าชมเว็บไซต์
- กำหนดค่า HTTP Request — ระบุ Endpoint ที่ JMeter ควร Hit
- ตั้งค่า Metric ที่เราสนใจ
มาดูทีละขั้นตอนสำหรับการกำหนดค่าการทดสอบง่ายๆ เพื่อจำลอง Request เดียวของผู้ใช้ไปหน้าหลักของแอปง่ายๆ ที่เราสร้างไว้ก่อนหน้านี้
เพิ่ม Thread Group
เลือก Add -> Threads (Users) -> Thread Group จากเมนูที่ขยายเมื่อคุณคลิกขวาที่ "Test Plan":
ระบุจำนวนผู้ใช้และ Attribute เพิ่มเติม:
กำหนดค่า HTTP Request
คลิกขวาที่ Thread ที่เราสร้างในขั้นตอนก่อนหน้า และเลือก Add -> Sampler -> HTTP Request:
กำหนด Protocol, Server Name, Port และ Path ของ Request:
ระบุมุมมองผลลัพธ์
คลิกขวาที่ HTTP Request และเลือก Add -> Listener -> View Results Tree:
การรันการทดสอบ
การทดสอบได้รับการกำหนดค่าแล้ว และเราสามารถ Trigger ได้ เพื่อทำเช่นนั้น ให้คลิกที่ปุ่ม Play สีเขียว:
ตามที่คุณเห็น แอปพลิเคชันผ่านการทดสอบ แต่เพียงแค่ Request เดียว ดังนั้นผลลัพธ์จึงชัดเจน คุณสามารถทดลองกับจำนวนผู้ใช้และตัวเลือกการกำหนดค่าอื่นๆ เพื่อดูว่าแอปพลิเคชันจะพฤติกรรมอย่างไร จากการทดสอบของฉัน แอปง่ายๆ เริ่ม Crash เมื่อมีผู้ใช้ประมาณ 200 คนเข้าถึงพร้อมกัน
ขั้นตอนต่อไป
หลังจากทำ Load Test แล้ว คุณจะรู้จุดอ่อนของแอปพลิเคชัน การเข้าใจขีดจำกัดผู้ใช้ คุณสามารถทำ Stress Test เพื่อดูว่าแอปพลิเคชันจะพฤติกรรมอย่างไร
Performance Tests ด้วย Ruby-prof
ฟีเจอร์ Performance Test ถูก Built-in ใน Rails จนถึงเวอร์ชัน 3 แล้วจึงถูกแยกออกไปเป็น Gem แยกต่างหาก https://github.com/rails/rails-perftest เนื่องจากฉันมีปัญหาบ้างในการใช้กับ Rails เวอร์ชันล่าสุด ฉันจึงตัดสินใจไม่รวมในบทความนี้ แทนที่จะใช้ library ruby-prof ที่ทำงานได้ดีมาก
ตามปกติ ขั้นตอนแรกคือเพิ่ม Gem ลงในแอปพลิเคชันของเรา:
bundle add ruby-prof
ขั้นตอนที่สองและขั้นตอนสุดท้ายของการกำหนดค่าคือ Update config/application.rb และใช้ Middleware สำหรับ Gem เพื่อให้ Library สามารถตรวจสอบ Request ของเราโดยอัตโนมัติและสร้างรายงานจากพวกมัน:
module Simpleapp class Application < Rails::Application config.middleware.use Rack::RubyProf, :path => './tmp/profile' end end
ตอนนี้คุณสามารถเข้าถึงแอปได้ และทุกครั้งที่คุณทำ Request Gem จะสร้างรายงานใหม่ มันจะมีลักษณะแบบนี้:
คุณสามารถหาได้ที่ Path ที่กำหนดค่าไว้ ซึ่งในกรณีนี้คือ tmp/profile รายงานที่สองก็ถูกสร้างขึ้นเช่นกัน และแสดง Call Stack ซึ่งก็เป็น Metric ที่ช่วยได้มากเมื่อ Debug ปัญหาประสิทธิภาพในแอปพลิเคชัน Rails
สำคัญที่ต้องจำว่า การตั้งค่า cache_classes และ cache_template_loading เป็น true จะทำให้แอปพลิเคชันช้าลงและท่วมท้น Metric ของแอปพลิเคชัน เนื่องจาก Rails จะพยายามโหลดไฟล์ที่จำเป็น
สรุป
การทดสอบเป็นส่วนสำคัญของทุกกระบวนการพัฒนา การตรวจสอบว่าโค้ดพฤติกรรมตามที่เราต้องการ มีความสำคัญเท่ากับการตรวจสอบว่าวิธีแก้ปัญหาของเรามีประสิทธิภาพดี การข้ามการทดสอบนำไปสู่ปัญหาร้ายแรงที่ส่งผลต่อประสิทธิภาพของแอปและความไว้วางใจของผู้ใช้ หวังว่าการทดสอบไม่ได้ยากนัก
ในบทความนี้ เราครอบคลุมด้านสำคัญต่อไปนี้ของการทดสอบ:
- เหตุผลที่คุณควรทดสอบโค้ดของคุณ
- ประเภทการทดสอบประสิทธิภาพที่แตกต่างกัน
- วิธีที่คุณสามารถทดสอบประสิทธิภาพของแอป Rails ของคุณ
หวังว่าตอนนี้คุณมั่นใจมากขึ้นว่าทำไมการเขียนการทดสอบจึงสำคัญ เพราะคุณรู้แล้วว่าทำไมและอย่างไร
หากคุณสนใจในการตรวจสอบประสิทธิภาพของแอปไม่ได้แค่ใน Local แต่ยังรวมถึงสภาพแวดล้อม Production หรือ Staging คุณควรดู AppSignal ด้วย
P.S. หากคุณต้องการอ่านโพสต์ Ruby Magic ทันทีที่เผยแพร่ สมัครรับข่าวสาร Ruby Magic newsletter และจะไม่พลาดโพสต์เด็ดๆ ไปเลย!