在資料工程領域,測試資料管道的可靠性和穩定性至關重要。然而,直接使用真實資料進行測試可能會帶來成本、效能和隱私等方面的問題。為瞭解決這些挑戰,本文將介紹如何使用測試替身和合成資料來最佳化資料管道測試流程。測試替身可以模擬資料函式庫連線等外部依賴,而合成資料則可以替代真實資料,從而降低測試成本、提高測試效率,並確保測試覆寫率。本文將會探討如何使用 Python 和相關函式庫,例如 pytest 和 Faker,來實作這些技術,並提供實際案例與程式碼範例,幫助讀者更好地理解和應用。
使用測試替身最佳化資料管道測試
在測試資料管道時,替換依賴項可以降低測試時間、複雜度和成本,並提高測試覆寫率。本章將探討如何使用測試替身(test double)來替換資料管道中的依賴項。
使用 db_engine 固定裝置進行單元測試
有多種方法可以在單元測試中使用 db_engine 固定裝置。其中一種方法是使用抽象化來提供不同的物件儲存可能性,包括用於測試的 MockStorage 類別。另一種方法是使用 mock.patch 來修補資料函式庫連線,其中 engine 或 conn 物件被修補。
使用測試資料函式庫
測試資料函式庫允許您避免使用雲端資料函式庫進行測試的成本。例如,官方的 Google Cloud SQL Python 示例展示瞭如何使用 Google 的聯結器函式庫與 sqlalchemy.create_engine 方法建立資料函式庫連線。在測試時,將引擎替換為測試資料函式庫,而不是 Cloud SQL 引擎。
使用測試資料函式庫並不妨礙您在 CI 管道中執行單元測試。第 5 章中介紹的 Docker Compose 技術可以用於在 CI 測試中包含測試資料函式庫容器。筆者曾經使用這種技術在 Google Cloud Build 中新增測試資料函式庫容器到建置組態中。
程式碼範例
import pytest
from unittest.mock import patch, MagicMock
from sqlalchemy import create_engine
@pytest.fixture
def db_engine():
engine = create_engine('sqlite:///:memory:')
# 初始化資料函式庫結構
return engine
def test_database_interaction(db_engine):
with patch('sqlalchemy.create_engine', return_value=db_engine):
# 執行資料函式庫互動的程式碼
pass
內容解密:
- 使用 pytest 固定裝置:
db_engine固定裝置建立了一個 SQLite 記憶體資料函式庫,用於測試。 - 模擬資料函式庫引擎:使用
patch將sqlalchemy.create_engine模擬為傳回測試用的db_engine。 - 執行測試:在模擬的資料函式庫環境中執行需要測試的資料函式庫互動程式碼。
進一步探索
為了進一步實踐本章介紹的技術,可以嘗試以下練習:
- 更多 Moto 模擬:為
hod_has_night_heron_data方法撰寫單元測試,以驗證指定字首是否有夜鷺內容的情況。 - 模擬放置:為
get_hod_matches方法設計一個測試方法,以驗證當沒有結果時的情況和有結果時的情況。
這些練習的可能解答可以在 test_exercises.py 和 test_database.py 中找到。
使用測試資料
在前一章中,您瞭解瞭如何替換資料管道測試中的兩個依賴項之一:外部服務介面。這讓您在成本效益高的測試方面邁進了一步。本章將介紹如何替換第7章中提到的第二個依賴項:外部資料來源。您將瞭解如何使用合成資料替換實時資料來源進行測試。
選擇實時資料或合成資料進行測試
在探討建立和使用合成資料之前,必須評估將資料依賴項替換為合成資料是否是正確的選擇。本章首先提供有關如何選擇實時資料或合成資料進行測試的指導,以及每種方法的優缺點。
合成資料生成方法
本章其餘部分將重點介紹不同的合成資料生成方法。首先介紹的手動資料生成方法可能是您在建立幾行假資料進行單元測試時已經做過的。從建立手動資料中獲得的經驗將有助於您構建自動化資料生成的準確模型。您還將瞭解如何使用資料生成函式庫自定義資料生成器,以提供測試所需的資料特徵。
手動資料生成
手動建立測試資料是常見的做法,尤其是在需要特定資料特徵的情況下。
自動化資料生成
透過構建模型自動生成資料,可以提高測試效率和覆寫率。您將瞭解如何使用資料生成函式庫來定製化資料生成。
保持資料生成模型的更新
透過將資料模式與測試生成程式碼連結,可以捕捉到源資料的變化,確保測試的有效性。
與實時資料合作
在建立和使用合成資料之前,必須記住,在某些情況下,連線到實時資料是更好的選擇。實時資料,例如來自生產環境的資料,可以是一個豐富的、規模龐大的、包含多種資料特徵的來源。如果使用生產資料的測試透過,那麼至少可以期望程式碼更改不會破壞生產環境。
優點
- 實時資料規模大,包含多種資料特徵。
- 如果測試透過,可以確保程式碼更改不會破壞生產環境。
缺點
- 需要避免在測試過程中損壞生產資料。
- 需要額外的步驟來連線和驗證資料來源。
- 涉及資料隱私問題,需要進行匿名化處理。
測試中使用實時資料的案例
曾經有一個產品,具有兩個資料函式庫:一個儲存查詢組態,另一個儲存查詢執行的源資料。由於這兩個資料函式庫都在不斷變化,我們無法用合成版本替換實時資料。相反,我們為本地開發建立了一個組態資料函式庫的副本,並連線到測試環境中的源資料函式庫。這種設定使我們能夠在本地環境中針對實時資料執行單元測試。
匿名化處理
在某些情況下,可能需要對資料進行匿名化處理,以刪除或遮蔽敏感資訊。在這種情況下,需要諮詢公司的法律和安全團隊,以確保測試結果的準確性。
對資源的影響
在測試中使用實時資料時,還需要考慮對服務或儲存資料的資源的影響。例如,如果資料函式庫正在處理和提供網站內容,則額外的測試負載可能會影響網站的效能。在這種情況下,使用專用的讀取副本或快照進行測試是一個好的替代方案。
程式碼示例與 Plantuml 圖表說明
測試環境中的資料函式庫關係:
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333
title 程式碼示例與 Plantuml 圖表說明
rectangle "連線" as node1
rectangle "提供資料" as node2
rectangle "執行單元測試" as node3
node1 --> node2
node2 --> node3
@enduml
此圖示描述了查詢組態資料函式庫、源資料函式庫、測試環境和本地開發環境之間的關係。查詢組態資料函式庫連線到源資料函式庫,源資料函式庫為測試環境提供資料,而測試環境中的單元測試則在本地開發環境中執行。
內容解密:
- 查詢組態資料函式庫:儲存查詢組態的資料函式庫。
- 源資料函式庫:儲存查詢執行的源資料的資料函式庫。
- 測試環境:用於執行測試的環境,連線到源資料函式庫。
- 本地開發環境:開發人員進行本地開發和測試的環境。
透過使用這樣的圖表,可以清晰地呈現出不同元件之間的關係和流程,有助於更好地理解系統架構和測試設定。
使用合成資料進行測試
在測試資料管道時,使用合成資料是一種常見的做法。合成資料是指透過手動或自動化過程建立的資料,而不是直接從資料來源(如API)取得的資料。在本章中,我們將探討使用合成資料的優點以及如何生成和利用合成資料進行測試。
合成資料的優點
使用合成資料的一個主要優點是,它不受限於生產環境中的資料。這意味著您可以建立涵蓋各種場景的測試資料,包括生產環境中可能不存在的情況。例如,在一個專案中,我們獲得了客戶的許可,使用其生產資料集進行測試。儘管該資料集很大,但它並不包含觸發我們正在除錯的資料函式庫查詢效能問題所需的正確資料特徵。
確保重要資料路徑被測試
透過使用合成資料,您可以確保所有重要的資料程式碼路徑都被測試到。這有助於發現和修復潛在的問題,從而提高資料管道的可靠性和穩定性。
無需客戶許可
另一個使用合成資料的優點是,您不需要獲得客戶的許可即可使用其生產資料。這在某些情況下可能是一個很大的優勢,尤其是當客戶的生產資料包含敏感資訊或受到嚴格的存取控制時。
幫助溝通重要資料特徵
合成資料還可以幫助向其他開發人員傳達重要的資料特徵。透過將合成資料包含在測試套件中,新開發人員可以快速瞭解專案的資料需求,而無需存取遠端系統中的實時資料。
消除對資料來源的連線
使用合成資料還可以消除對資料來源的連線,從而節省成本並簡化單元測試。這使得單元測試可以輕鬆地整合到持續整合(CI)流程中。
方便共用測試資料
將測試資料以JSON格式生成,可以幫助您在不同的工具、函式庫和語言之間共用該資料。此外,許多資料來源原生提供JSON,或者可以以JSON格式匯出。
使用合成資料進行迴歸測試
我最喜歡使用合成資料的一個方法是迴歸測試,即新增測試以確保以前的功能沒有丟失。當生產環境中出現與資料相關的錯誤時,您可以透過新增模擬導致錯誤的資料的測試資料,在測試中重現該錯誤。
提高測試的強健性
透過重現錯誤並修復它們,您將逐步提高測試的強健性。這不僅可以幫助您隔離根本原因並重現錯誤,還可以提高資料管道的整體品質。
內容解密:
上述JSON範例展示瞭如何以結構化的格式表示測試資料。其中包含了兩個測試物件的基本資訊,如id、name和age。這種格式方便在不同的工具和語言之間共用和解析。您可以根據需要擴充套件此結構,以包含更多欄位和複雜的資料關係,從而滿足各種測試需求。
生成合成資料的最佳實踐
生成合成資料時,應考慮以下最佳實踐:
- 確保資料的多樣性:生成的合成資料應涵蓋多種可能的場景和邊界條件,以確保測試的全面性。
- 保持與生產資料的一致性:儘管是合成資料,但仍應盡量保持與生產環境中的資料特徵一致,以確保測試結果的可靠性。
- 使用自動化工具:利用自動化工具生成合成資料,可以大大提高效率,並減少人為錯誤。
- 定期更新和維護:隨著資料管道和需求的變化,應定期更新和維護合成資料,以確保其持續滿足測試需求。
使用合成資料進行測試的挑戰與最佳實踐
在軟體開發過程中,測試是確保程式碼品質和穩定性的重要環節。然而,當面對複雜或龐大的資料來源時,建立合適的測試資料可能會變得困難或甚至不可能。合成資料(Synthetic Data)提供了一種解決方案,但它也伴隨著一些挑戰和限制。
合成資料的挑戰
- 建模複雜性:建立合成資料的過程中,很容易陷入過度複雜的建模中。開發者可能會被吸引去解決某些棘手的邊緣案例,結果導致花費過多時間在建模上。
- 過度設計:在建立合成資料時,如果沒有與待測試的程式邏輯同步進行,很容易產生與實際情況無關或過度測試的資料案例。
- 維護成本:合成資料需要持續的維護,以確保其與實際資料保持一致。如果維護成本過高,可能會導致合成資料過時或不可用。
程式碼範例:簡單的合成資料生成
import random
def generate_synthetic_data(num_records):
synthetic_data = []
for _ in range(num_records):
record = {
'id': random.randint(1, 1000),
'name': f'User {random.randint(1, 1000)}',
'age': random.randint(18, 80)
}
synthetic_data.append(record)
return synthetic_data
# 生成10筆合成資料
synthetic_data = generate_synthetic_data(10)
print(synthetic_data)
內容解密:
generate_synthetic_data函式接受一個引數num_records,表示要生成的合成資料筆數。- 在函式內部,使用迴圈生成指定數量的資料記錄,每筆記錄包含
id、name和age三個欄位。 id欄位使用random.randint(1, 1000)生成一個介於1到1000之間的隨機整數。name欄位根據隨機整數生成一個使用者名稱,格式為 “User X”,其中 X 是隨機整數。age欄位生成一個介於18到80之間的隨機年齡。- 每筆記錄被新增到
synthetic_data列表中,最終傳回該列表。
合成資料是否適合您的專案?
使用合成資料進行測試有其優點,但也可能帶來災難性的後果。以下是判斷合成資料是否適合您的專案的一些準則:
- 資料來源的特性:如果資料來源具有良好的特徵和穩定的結構,那麼合成資料可能是一個好的選擇。
- 維護成本:如果能夠自動或以最小的努力保持合成資料的更新,那麼它更有可能成功。
- 測試邏輯的範圍:對於邊界清晰的測試邏輯,合成資料可以提供有效的支援。
在決定是否使用合成資料之前,請考慮以下問題:
- 合成資料的不準確性可能造成的後果是什麼?
- 資料來源的複雜性和格式穩定性如何?
- 維護合成資料的成本和效益如何?
手動生成測試資料的實務應用與限制
在軟體開發與測試過程中,資料的生成與管理是一項重要任務。尤其是在處理複雜的資料轉換邏輯時,如何有效地生成與驗證測試資料,直接影響到測試的效率與品質。本章節將探討手動生成測試資料的方法、適用場景及其限制,並結合具體案例進行分析。
鳥類別調查資料的轉換邏輯
在鳥類別調查應用程式中,使用者可以上傳觀察資料,包括文字描述、圖片連結以及觀察到的鳥類別數量。這些原始資料需要經過轉換處理,以提取出有價值的資訊。例如,將使用者的文字描述中的特定鳥類別物種提取出來,並建立新的欄位進行儲存。
資料轉換範例程式碼
species_list = ["night heron", "great blue heron", "gray heron", "whistling heron"]
def apply_species_label(species_list, df):
species_regex = f".*({'|'.join(species_list)}).*"
return (df
.withColumn("description_lower", f.lower('description'))
.withColumn("species", f.regexp_extract('description_lower', species_regex, 1))
.drop("description_lower")
)
內容解密:
species_list包含了需要被提取的鳥類別物種名稱。apply_species_label函式接收物種列表和資料框架(DataFrame)作為輸入。- 使用正規表示式(regex)將
species_list中的物種名稱組合成一個比對模式。 - 將描述欄位(
description)轉換為小寫,以確保比對過程的大小寫不敏感。 - 使用
regexp_extract函式從描述欄位中提取第一個比對的物種名稱,並將結果儲存在新的species欄位中。 - 最後,刪除臨時建立的
description_lower欄位。
手動生成測試資料的適用場景
手動生成測試資料是一種簡單直接的方法,適合於以下場景:
- 測試範圍有限,例如驗證少數可能的 API 回應。
- 資料模型變更頻率較低。
- 需要的屬性數量較少(例如 10 個或更少)。
- 不需要驗證大型、稀疏資料集的處理邏輯。
手動測試資料範例
fake_data = [
{"description": "there was a night heron", ...},
{"description": "", ...},
{"description": "there was a heron", ...}
]
expected = [
{"user": "[email protected]", "species": "night heron"},
{"user": "[email protected]", "species": ""},
{"user": "[email protected]", "species": ""}
]
內容解密:
fake_data列表包含了模擬的使用者輸入資料,用於測試apply_species_label函式的正確性。expected列表定義了預期的輸出結果,用於與實際輸出進行比對,以驗證函式的正確性。- 測試案例涵蓋了不同的場景,包括完全比對、部分比對以及空描述等情況。
手動生成測試資料的限制
儘管手動生成測試資料具有簡單、易於實作的優點,但它也存在一些限制:
- 維護成本:隨著測試需求的變更,手動生成的測試資料需要相應地更新,這增加了維護成本。
- 覆寫率有限:手動生成的測試資料通常只能覆寫有限的場景,難以全面驗證複雜的邏輯。
自動化測試資料生成
在測試資料的準備過程中,自動化資料生成是一種比手動管理測試資料更好的選擇,尤其是在需要處理大量或複雜資料的情況下。考慮在以下情況下使用自動化資料生成:
- 需要測試的欄位數量龐大(寬資料集)
- 需要大量資料集(例如負載測試)
- 能夠將資料生成與規格(如資料模型或架構)繫結
- 需要根據值的分佈生成資料
合成資料函式庫
使用函式庫是一種建立合成資料的方法。Faker是一個Python函式庫,能夠生成從基本型別(如布林值和整數)到更複雜的資料(如句子、顏色和電話號碼)等各種型別的合成資料。
使用Faker生成合成資料
basic_fake_data方法展示瞭如何使用Faker建立合成資料來測試apply_species_label。這個例子使用了多個Faker內建的提供者,包括電子郵件、緯度和經度、檔案路徑和單詞:
def basic_fake_data():
fake = Faker()
fake_data = {
"user": fake.email(),
"location": fake.local_latlng(),
"img_files": [f"s3://bucket-name{fake.file_path(depth=2)}"],
"description": f"{' '.join(fake.words(nb=10))}",
"count": random.randint(0, 20),
}
for k, v in fake_data.items():
print(k, ":", v)
自定義生成資料
在使用像Faker這樣的合成資料提供者時,可能需要調整它們提供的值以符合所需的資料。在這個例子中,description欄位需要偶爾包含species_list中的元素。
DescriptionProvider類別實作了一個自定義的Faker提供者,其中的description方法增加了此功能:
class DescriptionProvider:
def description(self):
species = fake.words(ext_word_list=util.species_list, nb=randint(0,1))
word_list = fake.words(nb=10)
# 此處省略具體實作
內容解密:
- Faker函式庫的使用:首先,我們需要初始化一個Faker例項。然後,利用Faker提供的各種方法生成不同型別的資料,如電子郵件、地理位置、檔案路徑和隨機單詞。
- 自定義資料生成:透過建立自定義的Faker提供者,我們可以擴充套件Faker的功能,使其能夠生成特定格式或內容的資料。
- 自動化測試資料生成的優點:自動化測試資料生成能夠大大減少手動準備測試資料的工作量,並且能夠根據需求變化快速調整。
自動化資料生成的優點
- 無需手動更新所有測試案例,如果資料架構發生變化,只需更新Faker定義即可。
- 能夠根據需求生成大量或多樣化的測試資料。
重要注意事項
在使用像Faker這樣的函式庫生成合成資料時,務必驗證對所生成資料的假設。例如,在本章的開發過程中,發現Faker的電子郵件提供者在建立超過1,000個測試案例時不會生成唯一的結果。因此,在使用這些函式庫時,需要驗證它們所產生的資料。