
永遠不要忘記數(shù)據(jù)庫測試 數(shù)據(jù)庫測試的重要性和測試要點說明
譯者導(dǎo)讀:本文分為三部分,第一部分是第1節(jié),即說明“對數(shù)據(jù)庫測試的根本誤解”;第二部分從第2節(jié)至倒數(shù)第4節(jié),詳述“數(shù)據(jù)庫測試測什么”的問題;第三部分是最后3節(jié),引出“數(shù)據(jù)庫測試怎么測”的問題,提出自動部署、自動化測試、持續(xù)集成的思路及工具。另,副標題是俺自行添上去的,以明示本文意圖。
有許多關(guān)于測試驅(qū)動開發(fā)(Test-Driven Development,縮寫為TDD)的書籍。那些書通常關(guān)注的是將測試應(yīng)用于工作單元(units of work)。對于工作單元的理解有許多種不同的方式,通常它表示一個類(class)。正如那些書中所言:編寫許多測試,以使那些測試都能通過的方式創(chuàng)建代碼。應(yīng)模擬所有的外部資源,以便你可以只測試這個單元。
這很酷,但不幸的是所有的測試在此刻停止了。因為通常會有些沒被測到的查詢(手寫的或者是由某些ORM工具生成的)。有些程序員使用集成測試來測試那些查詢——連接到一個真實的數(shù)據(jù)庫并執(zhí)行真實的查詢來進行測試。這種做法通常意味著在測試快樂路徑(happy path)——我已經(jīng)有了ORM工具,所以它會搞定每件事,我無須費心。
數(shù)據(jù)庫通常是一家公司最有價值的資產(chǎn)。應(yīng)用程序可以一遍一遍重寫。舊的應(yīng)用程序扔出去,新的應(yīng)用程序裝進來。但是更換應(yīng)用程序時沒人會丟棄滿載數(shù)據(jù)的數(shù)據(jù)庫。而是將數(shù)據(jù)庫小心翼翼地遷移過去。位于多個系統(tǒng)中的許多不同的應(yīng)用程序會在同一時刻使用同一數(shù)據(jù)庫。這就是為什么擁有充滿約束的良好數(shù)據(jù)庫模型是如此重要、以及為什么應(yīng)謹慎對待數(shù)據(jù)庫的原因。你真的不想破壞數(shù)據(jù)一致性(consistency),因為這會使你的公司付出高昂的代價。
本文是關(guān)于經(jīng)常被遺忘的數(shù)據(jù)庫測試的。使用真實數(shù)據(jù)進行集成測試。實際上,它與你所使用的數(shù)據(jù)庫引擎的類型無關(guān)緊要。你可以使用PostgreSQL、MySQL、Oracle,或者甚至使用那些有趣的noSQL數(shù)據(jù)庫,例如MongoDB。以下規(guī)則可適用于各種數(shù)據(jù)庫和各類應(yīng)用程序。也許不是全部,例如noSQL數(shù)據(jù)庫就無法強制實施數(shù)據(jù)完整性(integrity)。
你的應(yīng)用程序通常是由許多不同的部件組成的。其中有一些<將任何你喜歡的語言放在這里>代碼,一些配置文件,一些SQL查詢,一些外部系統(tǒng)。測試一個應(yīng)用程序意味著分別測試每個部件(因為只有這樣才更容易找出bug)、以及測試所有部件是如何協(xié)作的。數(shù)據(jù)庫就是這些部件的其中之一,而且你應(yīng)該徹底測試它。
這是首要的、最可怕的錯誤。根本不測試數(shù)據(jù)庫。你編寫了一些使用數(shù)據(jù)庫的代碼。你甚至使用一些模擬數(shù)據(jù)庫連接為這些類創(chuàng)建了單元測試。
集成測試怎么樣?集成測試應(yīng)在生產(chǎn)環(huán)境下對應(yīng)用程序進行測試。集成測試背后的唯一想法是,確保應(yīng)用程序部署到生產(chǎn)環(huán)境后可以正常工作。如果你不在生產(chǎn)數(shù)據(jù)庫上測試應(yīng)用程序,那么你實際上不并不知道應(yīng)用程序能否工作。你的模擬連接讓你可以發(fā)送尚未檢查以及沒有檢查的任何查詢。模擬連接只返回你所需的數(shù)據(jù)。
不創(chuàng)建集成測試意味著你實際上沒有測試你的應(yīng)用程序。
我所觀察過的大多數(shù)團隊擁有某種形式的集成測試。通常進行快樂路徑測試:有某個ORM工具,我們持久化對象,ORM工具會完成那些工作,真是太酷了,我無須費心。
我從未見過一支對數(shù)據(jù)庫schema(模式/架構(gòu))進行測試的團隊。想象一下,由于某些針對產(chǎn)品的查詢很慢,因此你必須在該數(shù)據(jù)庫中創(chuàng)建某個索引。當下次在新的客戶環(huán)境中部署此應(yīng)用程序時,你希望擁有該索引并確認該索引真的就在那里。為什么不編寫一個簡單的測試來檢查該索引的存在呢?
除了索引,還有許多要測試的內(nèi)容:
當你開發(fā)某個應(yīng)用程序時,你可以從種類繁多的數(shù)據(jù)庫中進行選擇。通常你會從中選擇那個最好的、那個被團隊所熟知的、或者是由管理層所選定的(有時使用一些奇怪的理由)。有時同一應(yīng)用程序的多個部署會在同一時間使用不同的數(shù)據(jù)庫引擎。有時應(yīng)用程序會為了能使用不同的數(shù)據(jù)庫引擎進行準備,因此購買此應(yīng)用程序的客戶就可以選擇他想要的數(shù)據(jù)庫。
數(shù)據(jù)庫引擎的選擇真的與進行產(chǎn)品測試無關(guān)。
由于程序員的懶惰,因此他們希望他們的測試可以運行得飛快。他們不想為測試結(jié)果等太久。這也就是為什么許多團隊使用某些更快的內(nèi)存數(shù)據(jù)庫(例如HSQLDB)的原因。由于那些內(nèi)存數(shù)據(jù)庫僅存儲在RAM(Random Access Memory,隨機存取存儲器)內(nèi)存中,而且在操作時不接觸任何硬盤驅(qū)動器,因此它們的運行速度要快得多。
還有一條常常被人遺忘的規(guī)則:
測試應(yīng)該使用與生產(chǎn)環(huán)境相同的數(shù)據(jù)庫引擎。
許多程序員會使用某個其他引擎。常見的解釋很簡單:“我們的數(shù)據(jù)庫太慢了,我們應(yīng)該使用某個內(nèi)存數(shù)據(jù)庫引擎。”。這并不是個好主意。這樣你測試的是該其他引擎,而非你的生產(chǎn)環(huán)境中的那個,所以實際上你并沒對你的應(yīng)用程序進行測試。
我曾經(jīng)遇到過這個問題。我們必須在成功連接數(shù)據(jù)庫后通過設(shè)置session變量來優(yōu)化查詢。那個應(yīng)用程序在生產(chǎn)環(huán)境中只使用Oracle數(shù)據(jù)庫。當設(shè)置此變量以后,測試失敗了。而且是所有的測試都失敗了。原來是我不能在HSQLDB內(nèi)存數(shù)據(jù)庫中設(shè)置此變量,因為它根本不存在。因此,我必須編寫一段糟糕的代碼:在連接后,檢查數(shù)據(jù)庫引擎,并由此決定是否設(shè)置此變量。
即使你沒有任何與混合引擎有關(guān)的問題,請記住,當你測試其他非生產(chǎn)環(huán)境的數(shù)據(jù)庫引擎時,你恰恰根本沒對你的應(yīng)用程序進行測試。
測試有一個通用的、明確定義的流程。 它非常簡單:
若嘗試背道而馳,則你會被它所傷。
你察覺在測試之后是沒有整理(tidying)的么?
這點非常重要:必須在測試前準備環(huán)境,而非測試之后。你無法確保測試將能夠清理一切。應(yīng)用程序可能因為某個錯誤、網(wǎng)絡(luò)連接失敗而退出,或者應(yīng)用程序可能崩潰(例如,由于內(nèi)存不足異常)。該測試如何終止并不重要,真正重要的是該測試運行在為每個測試所準備的相同環(huán)境中。
我曾犯過這個錯誤:有大量的集成測試,它們會在每次測試后清理所有更改。許多程序員正使用調(diào)試器來運行這些測試,并且當發(fā)現(xiàn)bug后會在中間停止測試。任何在該測試之后運行的測試會得到不可預(yù)知的且隨機的結(jié)果。因為它正運行在已被前一測試所改變的環(huán)境中,而且沒有為其清理整個環(huán)境。
在前面的部分中我提到許多有關(guān)準備數(shù)據(jù)庫的內(nèi)容。我還想補充一點。準備數(shù)據(jù)庫是不夠的。當你通過清理某些表、加載配件等等準備好數(shù)據(jù)庫時……還剩下一件事情要做。
在準備完畢后,要檢查數(shù)據(jù)庫狀態(tài)。
你真正需要確保的是你已將一切準備妥當。當出現(xiàn)由于bug所導(dǎo)致的某些數(shù)據(jù)留下來且未能清理時,這些工作就可以節(jié)省你的時間。
這應(yīng)該是測試前數(shù)據(jù)庫準備的一部分。
每個應(yīng)用程序都需要某種形式的安裝過程。而對于你部署數(shù)據(jù)庫而言,永遠是第一次。
程序員往往會通過手工執(zhí)行某些臨時的數(shù)據(jù)定義語言(DDL,data definition language)查詢來改變數(shù)據(jù)庫。他們稍后并沒有把那些語句寫下來,或是忘記了所做的改動。他們沒有更新安裝腳本。大多數(shù)團隊不使用有版本控制的腳本(例如,Ruby on Rails中的Migrations、或者是Java世界中的Liquibase)。
對測試而言最好的方式是,在運行測試套件前重新創(chuàng)建數(shù)據(jù)庫。你不必在每個測試開始前都那么做。只在運行所有測試前運行一次就行了。
是的,寧可事先謹慎有余,不要事后追悔莫及。
外鍵是提供數(shù)據(jù)庫一致性的基本途徑之一。在良好的關(guān)系數(shù)據(jù)庫schema中,你應(yīng)該擁有各種鍵。如果你沒有的話,那么這可能表明你有一個真正的大問題。然而,這取決于數(shù)據(jù)模型,但是通常缺乏外鍵是種非常糟糕設(shè)計的味道。
測試外鍵很簡單。只需在事先沒有在引用表中添加適當?shù)男械那闆r下,為某個表添加一些行。你應(yīng)該得到一個錯誤。然后,你應(yīng)該從引用表中刪除行,你可能得到錯誤,或沒有錯誤(這取決于該鍵的定義)。無論如何,你都應(yīng)該檢查一下預(yù)期行為。
在良好的數(shù)據(jù)庫設(shè)計中,你應(yīng)該定義一些合理的默認值。通常這些默認值可能是空(null)。即便這些空也應(yīng)該進行測試。你不能假設(shè),只有你的應(yīng)用程序?qū)⒏淖兇藬?shù)據(jù)庫中的數(shù)據(jù)。\ 兩個問題:
在數(shù)據(jù)庫中還有更多約束,不僅只有主鍵和外鍵約束。你可能擁有一些唯一的(unique)或不為空的列。你可能約束某列只有很少的值集。你可能想確保價格永遠不會低于0。
良好的數(shù)據(jù)庫schema應(yīng)擁有許多約束。你也應(yīng)該測試它們。如果你希望你的價格列只能擁有正值,當你嘗試向其中插入-1美元時會發(fā)生些什么?為什么不測試一下呢?
你不能假設(shè)只有你的經(jīng)過良好測試的應(yīng)用程序?qū)⑹褂媚切?shù)據(jù),而且這些檢查是為你防御這些bug的最后一道防線。為什么不測試它是否正常工作?
通常,數(shù)據(jù)庫測試會更改數(shù)據(jù)庫。你可能同時運行多個測試,但是你必須確保那些測試彼此之間沒有影響。你必須確保,如果某個測試將一些內(nèi)容寫入數(shù)據(jù)庫,而另一測試將不會讀到。
通常,很容易搞得一塌糊涂,因此我小小的忠告是:避免在同一時間運行多個測試。這也意味著,你不應(yīng)該在多臺機器運行相同的測試套件。
當你有許多想運行測試的開發(fā)者時,他們每個人應(yīng)該擁有可用于編寫測試的單獨的數(shù)據(jù)庫。如果你擁有某種形式的只讀數(shù)據(jù)庫,沒關(guān)系,多臺機器可以在同一時間使用這個數(shù)據(jù)庫。但是如果你允許所有程序員使用同一數(shù)據(jù)庫進行測試的情形出現(xiàn),那么你可能真的會得到不可預(yù)知的測試結(jié)果。
當程序員在某個測試中發(fā)現(xiàn)一個錯誤時會怎么做?那么,優(yōu)秀的程序員會盡量修正錯誤。如果該測試失敗僅僅是因為另一程序員在同一個數(shù)據(jù)庫上運行他的測試所導(dǎo)致的話,那么修正此類錯誤只是在浪費程序員的時間。
優(yōu)秀的程序員是懶惰的。如果你命令優(yōu)秀的程序員每次都重復(fù)同樣的任務(wù),他們會越來越沮喪。優(yōu)秀的程序員會自動化可重復(fù)的事情。
在每個項目中,你必須在測試環(huán)境中部署某些東西。做這些會花去多少時間?你真的想為了重新部署應(yīng)用程序和加載數(shù)據(jù)庫一直浪費你的程序員時間么?
這就是為什么每個項目都應(yīng)該有個大紅按鈕的原因。某位程序員可以按下此按鈕,然后沖杯咖啡,回去工作,并且?guī)追昼姾蟮弥拇蠹t色按鈕完成的工作,一切準備就緒。
大紅按鈕真的會為你節(jié)省很多時間。你會說自動化所有工作實在太緩慢。然而,事實并非如此。恰恰相反,就像說測試驅(qū)動開發(fā)(TDD,Test-Driven Development)很慢一樣。在最初的時候比較慢,但隨著項目變得更加復(fù)雜,由于存在測試或按鈕,會為你節(jié)省更多的時間。各種各樣的大紅按鈕——你可以用它們部署應(yīng)用程序、運行測試,以及類似的后方支援。
有時會使用Jenkins(又名Hudson)來做這些。是的,對于此類大紅按鈕而言這是一款偉大的軟件。唯一的事情是,每位程序員應(yīng)該有其自己的一組工作以便在其自己的環(huán)境中部署所有的內(nèi)容,在其自己的環(huán)境中他(或她,當然)可以自由發(fā)揮,而不會影響他人,同樣也不會受到他人的影響。
有許多數(shù)據(jù)庫測試工具。為了測試整個schema,你可以編寫簡單的集成測試。對于PostgreSQL有pgTAP,使用TAP插件它可以與Jenkins集成到一起,因此Jenkins可以擁有一項用于測試數(shù)據(jù)庫(包括生產(chǎn)數(shù)據(jù)庫)是否正常的工作。
數(shù)據(jù)分析咨詢請掃描二維碼
若不方便掃碼,搜微信號:CDAshujufenxi
訓(xùn)練與驗證損失驟升:機器學(xué)習(xí)訓(xùn)練中的異常診斷與解決方案 在機器學(xué)習(xí)模型訓(xùn)練過程中,“損失曲線” 是反映模型學(xué)習(xí)狀態(tài)的核心指 ...
2025-09-19解析 DataHub 與 Kafka:數(shù)據(jù)生態(tài)中兩類核心工具的差異與協(xié)同 在數(shù)字化轉(zhuǎn)型加速的今天,企業(yè)對數(shù)據(jù)的需求已從 “存儲” 轉(zhuǎn)向 “ ...
2025-09-19CDA 數(shù)據(jù)分析師:讓統(tǒng)計基本概念成為業(yè)務(wù)決策的底層邏輯 統(tǒng)計基本概念是商業(yè)數(shù)據(jù)分析的 “基礎(chǔ)語言”—— 從描述數(shù)據(jù)分布的 “均 ...
2025-09-19CDA 數(shù)據(jù)分析師:表結(jié)構(gòu)數(shù)據(jù) “獲取 - 加工 - 使用” 全流程的賦能者 表結(jié)構(gòu)數(shù)據(jù)(如數(shù)據(jù)庫表、Excel 表、CSV 文件)是企業(yè)數(shù)字 ...
2025-09-19SQL Server 中 CONVERT 函數(shù)的日期轉(zhuǎn)換:從基礎(chǔ)用法到實戰(zhàn)優(yōu)化 在 SQL Server 的數(shù)據(jù)處理中,日期格式轉(zhuǎn)換是高頻需求 —— 無論 ...
2025-09-18MySQL 大表拆分與關(guān)聯(lián)查詢效率:打破 “拆分必慢” 的認知誤區(qū) 在 MySQL 數(shù)據(jù)庫管理中,“大表” 始終是性能優(yōu)化繞不開的話題。 ...
2025-09-18DSGE 模型中的 Et:理性預(yù)期算子的內(nèi)涵、作用與應(yīng)用解析 動態(tài)隨機一般均衡(Dynamic Stochastic General Equilibrium, DSGE)模 ...
2025-09-17Python 提取 TIF 中地名的完整指南 一、先明確:TIF 中的地名有哪兩種存在形式? 在開始提取前,需先判斷 TIF 文件的類型 —— ...
2025-09-17CDA 數(shù)據(jù)分析師:解鎖表結(jié)構(gòu)數(shù)據(jù)特征價值的專業(yè)核心 表結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 規(guī)范存儲的結(jié)構(gòu)化數(shù)據(jù),如數(shù)據(jù)庫表、Excel 表、 ...
2025-09-17Excel 導(dǎo)入數(shù)據(jù)含缺失值?詳解 dropna 函數(shù)的功能與實戰(zhàn)應(yīng)用 在用 Python(如 pandas 庫)處理 Excel 數(shù)據(jù)時,“缺失值” 是高頻 ...
2025-09-16深入解析卡方檢驗與 t 檢驗:差異、適用場景與實踐應(yīng)用 在數(shù)據(jù)分析與統(tǒng)計學(xué)領(lǐng)域,假設(shè)檢驗是驗證研究假設(shè)、判斷數(shù)據(jù)差異是否 “ ...
2025-09-16CDA 數(shù)據(jù)分析師:掌控表格結(jié)構(gòu)數(shù)據(jù)全功能周期的專業(yè)操盤手 表格結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 存儲的結(jié)構(gòu)化數(shù)據(jù),如 Excel 表、數(shù)據(jù) ...
2025-09-16MySQL 執(zhí)行計劃中 rows 數(shù)量的準確性解析:原理、影響因素與優(yōu)化 在 MySQL SQL 調(diào)優(yōu)中,EXPLAIN執(zhí)行計劃是核心工具,而其中的row ...
2025-09-15解析 Python 中 Response 對象的 text 與 content:區(qū)別、場景與實踐指南 在 Python 進行 HTTP 網(wǎng)絡(luò)請求開發(fā)時(如使用requests ...
2025-09-15CDA 數(shù)據(jù)分析師:激活表格結(jié)構(gòu)數(shù)據(jù)價值的核心操盤手 表格結(jié)構(gòu)數(shù)據(jù)(如 Excel 表格、數(shù)據(jù)庫表)是企業(yè)最基礎(chǔ)、最核心的數(shù)據(jù)形態(tài) ...
2025-09-15Python HTTP 請求工具對比:urllib.request 與 requests 的核心差異與選擇指南 在 Python 處理 HTTP 請求(如接口調(diào)用、數(shù)據(jù)爬取 ...
2025-09-12解決 pd.read_csv 讀取長浮點數(shù)據(jù)的科學(xué)計數(shù)法問題 為幫助 Python 數(shù)據(jù)從業(yè)者解決pd.read_csv讀取長浮點數(shù)據(jù)時的科學(xué)計數(shù)法問題 ...
2025-09-12CDA 數(shù)據(jù)分析師:業(yè)務(wù)數(shù)據(jù)分析步驟的落地者與價值優(yōu)化者 業(yè)務(wù)數(shù)據(jù)分析是企業(yè)解決日常運營問題、提升執(zhí)行效率的核心手段,其價值 ...
2025-09-12用 SQL 驗證業(yè)務(wù)邏輯:從規(guī)則拆解到數(shù)據(jù)把關(guān)的實戰(zhàn)指南 在業(yè)務(wù)系統(tǒng)落地過程中,“業(yè)務(wù)邏輯” 是連接 “需求設(shè)計” 與 “用戶體驗 ...
2025-09-11塔吉特百貨孕婦營銷案例:數(shù)據(jù)驅(qū)動下的精準零售革命與啟示 在零售行業(yè) “流量紅利見頂” 的當下,精準營銷成為企業(yè)突圍的核心方 ...
2025-09-11