「數據庫沒有單元測試」是技術債務的溫床。Visual Studio Database Edition (DBPro/Data Dude) 將測試驅動開發 (TDD)引入數據庫專案:Schema 比對、靜態分析、單元測試 (T-SQL/CLR)、數據生成、部署驗證、版本控制一體化。本文構建從需求到生產的 TDD 閉環流程。
數據庫 TDD 核心流程 (紅-綠-重構)
- 紅:編寫失敗的單元測試 (定義預期行為);
- 綠:編寫最少 T-SQL/CLR 代碼通過測試;
- 重構:優化代碼結構、性能、命名,測試保綠;
- 提交:測試 + 代碼 入庫,CI 自動驗證。
DBPro 單元測試架構
| 組件 | 角色 | 關鍵特性 |
| 數據庫專案 (.dbproj) | Schema 來源 (創建腳本、預部署/後部署) | 版本控制、Schema 比對、建置驗證 |
| 測試專案 (.testproj) | 單元測試容器 (C# 承載 T-SQL 測試) | MSTest 框架、有序/並行執行、數據驅動 |
| 測試類/方法 | 對應存儲過程/函數/觸發器/視圖 | `[TestMethod]`、`[TestInitialize]`、`[TestCleanup]` |
| 測試條件 | 斷言邏輯 (行數、標量值、結果集、執行時間、空結果集) | 內建 8 種條件,可擴展自定義條件 |
| 測試數據生成計劃 | 生成符合約束的測試數據 (發生器、分布、外鍵一致性) | 預部署腳本、後部署腳本、數據綁定 |
| 部署配置 | 目標實例 (本地/共享/隔離)、部署動作 (創建/更新/重建) | 沙箱隔離、事務回滾、快照還原 |
標準化測試模板 (存儲過程示例)
-- 1. 定義測試數據 (預部署腳本 / 數據生成計劃)
INSERT INTO dbo.Customers (Id, Name, Email, Status) VALUES
(1, 'Alice', 'alice@test.com', 'Active'),
(2, 'Bob', 'bob@test.com', 'Inactive');
-- 2. 測試方法 (C# 承載 T-SQL)
[TestMethod]
public void GetActiveCustomers_ReturnsOnlyActive()
{
// Arrange: 測試數據已由部署/生成計劃準備
// Act
using (var cmd = new SqlCommand("dbo.GetActiveCustomers", conn) { CommandType = CommandType.StoredProcedure })
using (var reader = cmd.ExecuteReader())
{
// Assert: 測試條件驗證
Assert.IsTrue(reader.Read()); // 至少一行
Assert.AreEqual("Alice", reader["Name"]);
Assert.AreEqual("Active", reader["Status"]);
Assert.IsFalse(reader.Read()); // 僅一行
}
}
三大測試隔離策略
| 策略 | 機制 | 優點 | 缺點 | 適用場景 |
| 事務回滾 | `TransactionScope` / `BEGIN TRAN ... ROLLBACK` | 極快、無殘留、並行安全 | 不支持測試 `COMMIT` 邏輯、分布式事務、DDL | 單元測試首選 (90%+) |
| 快照還原 | 數據庫快照 / 備份還原 / 容器重建 | 支持 DDL、完整隔離、狀態重現 | 慢 (秒~分)、資源重、串行 | 集成測試、Schema 遷移測試 |
| 數據重建 | `TRUNCATE` + 重新插入 / `MERGE` 同步 | 中等速度、可控制數據狀態 | 外鍵順序、觸發器、標識列重置複雜 | 無事務支持場景 (MyISAM 等) |
進階模式:數據驅動測試 + 邊界覆蓋
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", "TestCases.csv", "TestCases#csv", DataAccessMethod.Sequential)]
[TestMethod]
public void CalculateDiscount_DataDriven()
{
var input = Convert.ToDecimal(TestContext.DataRow["InputAmount"]);
var expected = Convert.ToDecimal(TestContext.DataRow["ExpectedDiscount"]);
var customerType = TestContext.DataRow["CustomerType"].ToString();
using (var cmd = new SqlCommand("dbo.CalculateDiscount", conn) { CommandType = CommandType.StoredProcedure })
{
cmd.Parameters.AddWithValue("@Amount", input);
cmd.Parameters.AddWithValue("@CustomerType", customerType);
var result = (decimal)cmd.ExecuteScalar();
Assert.AreEqual(expected, result, 0.001m, $"Failed for {customerType} amount {input}");
}
}
- CSV/Excel/XML/SQL Server 數據源;
- 覆蓋:正常值、邊界值 (0、MAX)、異常值 (NULL、負數)、業務規則邊界 (VIP 閾值);
- 新增測試用例 = 新增 CSV 行,**零代碼變更**。
CI/CD 管道集成
# Azure DevOps / GitHub Actions 片段
- task: VSBuild@1
inputs:
solution: 'DatabaseProject.dbproj'
platform: 'Any CPU'
configuration: 'Release'
- task: VSTest@2
inputs:
testSelector: 'testAssemblies'
testAssemblyVer2: '**/*Test*.dll'
searchFolder: '$(Build.BinariesDirectory)'
runSettingsFile: 'DatabaseTest.runsettings' # 連接字符串、部署配置
codeCoverageEnabled: true
- task: PublishTestResults@2
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '**/*.trx'
避坑指南
- 測試邏輯洩露生產代碼:測試項目**獨立編譯、獨立部署**,不引用生產專案輸出;
- 共享開發數據庫導致測試不穩定:強制**本地實例 / 容器 / 專用測試實例**,CI 獨立佈建;
- 忽視性能基準測試:`ExecutionTime` 條件設置基準 (P95 < 200ms),防止性能回歸;
- 測試數據不符合約束:生成計劃**必須滿足 FK/CK/UK**,否則測試失效掩蓋真實 Bug;
- 過度測試內部實現:測試**公共契約 (參數、結果集、副作用)**,不測試私有邏輯/中間表。
總結
數據庫 TDD 非理想主義,而是將 Schema 變更、邏輯重構、性能優化的風險從「上線後發現」前移到「編碼時發現」的工程紀律。DBPro/SSDT 提供了工具鏈,關鍵在於團隊建立:**測試先行文化、隔離環境標準化、數據生成自動化、CI 門禁強制**。從「手工驗證」進化到「自動化契約驗證」,是數據庫工程化的必經之路。
- 上一篇文章: 通用分頁存儲過程 SQL 注入漏洞深度剖析:動態 SQL 拼接風險與參數化防禦實戰
- 下一篇文章: 由外而內提升 SQL Server 效能:網路 → 客戶端 → 伺服器 → 資料庫
Copyright ©
程式碼之家 All Rights Reserved