iT邦幫忙

2025 iThome 鐵人賽

DAY 8
0

在前幾天的學習中,我們已掌握了 Locust 的基本用法,並學會管理 Cookie 和 Session。今天,將深入探討一個在實際測試中至關重要的主題:參數化測試。透過參數化,可以讓測試腳本變得更加真實、多樣化,進而模擬不同使用者的行為模式,建立更動態且靈活的壓力測試。

為什麼需要參數化測試?

在真實的應用場景中,使用者的行為總是充滿多樣性。如果測試總是使用相同的資料或請求路徑,測試結果可能無法真實反映系統在實際使用中的表現。參數化測試能有效解決以下問題:

1. 模擬真實使用者行為

真實使用者會使用不同的帳號、查詢不同的資料、輸入各種參數。固定的測試資料無法模擬這種多樣性。

# ❌ 不真實的測試 - 所有使用者都查詢相同資料
@task
def search_products(self):
    response = self.client.get("/search?q=iPhone")  # 總是搜尋 iPhone

# ✅ 真實的測試 - 使用者搜尋不同商品
@task
def search_products(self):
    search_terms = ["iPhone", "Samsung", "iPad", "MacBook", "AirPods"]
    query = random.choice(search_terms)
    response = self.client.get(f"/search?q={query}")

2. 測試系統的資料處理能力

不同的資料可能會觸發系統不同的處理邏輯。使用多樣化的測試資料能夠更全面地測試系統,發現潛在的效能瓶頸或邏輯錯誤。

3. 避免快取影響測試結果

如果所有請求都使用相同的參數,系統的快取機制可能會產生誤導性的測試結果,導致測試不準確。

# ❌ 可能被快取影響
@task
def get_user_profile(self):
    response = self.client.get("/users/123")  # 總是查詢相同使用者

# ✅ 避免快取影響
@task
def get_user_profile(self):
    user_id = random.randint(1, 10000)
    response = self.client.get(f"/users/{user_id}")

4. 驗證邊界條件和錯誤處理

參數化測試可以系統性地涵蓋各種邊界條件和異常情境,確保系統的魯棒性。

一般參數化方式

Locust 提供了多種參數化方式,讓你可以靈活地為測試提供不同的資料。以下是一些常見方法:

1. 使用 Python 內建的資料結構

最簡單的參數化方式是使用列表、字典等資料結構,並結合 random 模組進行隨機選擇。

import random
from locust import HttpUser, task, between

class BasicParameterizedUser(HttpUser):
    wait_time = between(1, 3)
    host = "http://localhost:8080"
    
    # 定義測試資料
    search_keywords = ["Python", "JavaScript", "Java", "Go", "Rust"]
    product_categories = ["electronics", "books", "clothing", "home", "sports"]
    
    @task(3)
    def search_with_random_keyword(self):
        """使用隨機關鍵字搜尋"""
        keyword = random.choice(self.search_keywords)
        self.client.get(f"/search?q={keyword}")
    
    @task(2)
    def browse_category(self):
        """瀏覽隨機分類"""
        category = random.choice(self.product_categories)
        page = random.randint(1, 10)
        self.client.get(f"/category/{category}?page={page}")

2. 使用加權隨機選擇

在某些情況下,我們希望某些參數出現的頻率更高,以模擬真實世界中的使用者行為模式。

import random
from locust import HttpUser, task, between

class WeightedParameterizedUser(HttpUser):
    wait_time = between(1, 2)
    host = "http://localhost:8080"
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # 使用加權列表 - 模擬真實使用頻率
        self.search_terms = [
            ("iPhone", 30),  # 高頻搜尋
            ("Samsung", 25),
            ("iPad", 20),
            ("MacBook", 15),
            ("AirPods", 10)  # 低頻搜尋
        ]
        
        # 建立加權選擇列表
        self.weighted_terms = []
        for term, weight in self.search_terms:
            self.weighted_terms.extend([term] * weight)
    
    @task
    def weighted_search(self):
        """根據權重進行搜尋"""
        search_term = random.choice(self.weighted_terms)
        self.client.get(f"/search?q={search_term}")

3. 使用 itertools 進行循環參數化

當我們希望按順序使用參數時,可以使用 itertools.cycle 建立一個無限循環的迭代器。

import itertools
from locust import HttpUser, task, between

class CyclicParameterizedUser(HttpUser):
    wait_time = between(1, 2)
    host = "http://localhost:8080"
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # 建立循環迭代器
        self.user_ids = itertools.cycle(range(1, 101))  # 循環使用 1-100
        self.api_versions = itertools.cycle(["v1", "v2", "v3"])
    
    @task
    def cyclic_user_request(self):
        """循環使用使用者 ID"""
        user_id = next(self.user_ids)
        api_version = next(self.api_versions)
        
        headers = {"API-Version": api_version}
        self.client.get(f"/users/{user_id}", headers=headers)

藉由讀取 CSV 檔案進行參數化

對於需要大量且結構化的測試資料,CSV 檔案 是最實用的方法。它易於管理,並能容納龐大的資料集。

首先,建立一個名為 test_users.csv 的檔案,內容如下:

user_id,username,email,user_type,region,preferred_language
1,john_doe,john@example.com,premium,US,en
2,alice_wang,alice@example.com,basic,TW,zh-TW
...

接著,在 Locust 腳本中加入讀取 CSV 檔案的邏輯:

import csv
import random
import itertools
from locust import HttpUser, task, between

class CSVParameterizedUser(HttpUser):
    wait_time = between(1, 3)
    host = "http://localhost:8080"
    
    # 類別變數,所有使用者實例共享
    test_users = []
    
    @classmethod
    def load_test_data(cls):
        """載入 CSV 測試資料"""
        try:
            with open('test_users.csv', 'r', encoding='utf-8') as file:
                reader = csv.DictReader(file)
                cls.test_users = list(reader)
                print(f"成功載入 {len(cls.test_users)} 筆測試資料")
        except FileNotFoundError:
            print("找不到 test_users.csv 檔案,使用預設資料")
            cls.test_users = [{"user_id": "1", "username": "test_user"}]
    
    def on_start(self):
        """使用者開始時載入資料"""
        # 在第一個使用者實例化時載入資料
        if not self.__class__.test_users:
            self.__class__.load_test_data()
        
        # 建立使用者特定的資料迭代器
        self.user_data = next(itertools.cycle(self.__class__.test_users))
    
    @task(3)
    def get_user_profile(self):
        """使用 CSV 資料獲取使用者個人資料"""
        self.client.get(f"/users/{self.user_data['user_id']}")
    
    @task(2)
    def update_preferences(self):
        """根據 CSV 資料更新使用者偏好設定"""
        user_data = random.choice(self.__class__.test_users)
        update_data = {
            "language": user_data['preferred_language'],
            "region": user_data['region'],
        }
        self.client.put(
            f"/users/{user_data['user_id']}/preferences", 
            json=update_data
        )

提示: 在上述範例中,test_users 被設為類別變數,並在 on_start__init__ 中以類別方法的方式載入資料,這能有效避免在每次創建使用者時都重複讀取檔案,大幅提升效能。

總結

今天我們深入學習了 Locust 中的參數化測試技術:

  1. 參數化的重要性:了解了為什麼需要參數化以及它能解決哪些問題。
  2. 基本參數化方法:學會了使用 Python 內建資料結構進行參數化。
  3. 進階參數化技巧:掌握了加權選擇、循環迭代、動態生成等進階技術。
  4. CSV 檔案應用:學會了如何使用 CSV 檔案管理大量測試資料。
  5. 最佳實踐:了解了資料管理、效能優化等重要原則。

透過掌握這些參數化技術,你現在能夠建立更真實的測試場景、管理大量的測試資料、並提升測試的準確性。參數化是進階壓力測試的核心技能,正確使用這些技術將能讓你的測試更貼近真實世界的使用情況,從而發現更多潛在問題並提供更可靠的測試結果。


上一篇
Day07 - Locust 進階技巧:Cookie 處理與 Session 管理
下一篇
Day 09 - Locust 負載模型:Linear vs Step Load Mode 詳細分析
系列文
Vibe Coding 後的挑戰:Locust x Loki 負載及監控13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言