谈一下我们是怎么做数据库单元测试(Database Unit Test)的
背景介紹
最近在團隊在做release之前的regression,把各個feature分支merge回master之后發現DB的單元測試出現了20多個失敗的test cases。之前沒怎么做過DB的單元測試,正好借這個機會熟悉一下寫DB單元測試的流程。
這篇博文中首先介紹一下在我們的特定項目場景中是如何搭建DB 單元測試框架的,然后舉一個簡單的例子,從頭到尾在visual studio中創建一個簡單的單元測試工程。
我們開發的產品使用的數據庫為Sql Server,總共有400多張表,2000多個存儲過程,每個存儲過程都相當于應用代碼中的一個功能函數。代碼中的每個復雜的功能函數都可以通過寫單元測試來在一定程度上保證代碼質量,存儲過程也如此。代碼中的UT難點在于解耦,也就把相互牽連在一起的代碼彼此分離開來,各個擊破,例如A函數需要B函數提供的數據,測試A函數的時候我們只想測試A函數,不想調用B,這時候就需要我們自己提供B函數生成的數據。這叫做mock。
在做DB單元測試的時候,存儲過程所使用的數據比較特殊,都是持久化在數據庫表中的,2000多個存儲過程增刪改查400多個表,我們需要把這些表的數據為每個存儲過程做隔離,如果測試用例使用的數據相互之間關聯,恐怕會天下大亂,因為在一般情況下,單元測試用例的運行順序都是隨機的,如果單元測試使用的數據有關聯,很有可能兩次運行結果也是隨機的(但是有一種方法可以固定case執行順序,我在最后的例子中進行說明),我們這次的20多個失敗的cases就有這種原因導致的,兩臺機器上跑出的結果不一樣,有的成功,有的失敗。
注:有關單元測試的定義,見另外一篇帖子,單元測試有毒
那么問題就來了,如何才能做數據的隔離呢?說一下我們的方案。
準備數據
我們創建了一個基準的數據庫,做出一個備份,叫做base.bak,這個版本比較低,比如是2.8,這里面包含了一些測試的基本數據。然后我們創建了另外一個preparation的工程,用于把base.bak升級到當前release版本,例如,當前release的版本為2.18。這個工程同時也測試了升級的流程。升級成功之后,把這個數據庫在本地做一個備份release_2_18.bak。好了,數據都準備好了。
測試需要注意的要點
四個函數
對于微軟的這個DB UT測試框架,有四個函數需要搞清楚,因為這可能影響你的測試結果:
[ClassInitialize]public static void ClassInitialize(TestContext testContext){... } [ClassCleanup]public static void ClassCleanup(){... } [TestInitialize()]public void TestInitialize(){... } [TestCleanup()]public void TestCleanup(){ ? ? ? ? ? ?... }顧名思義,ClassInitialize() 是在每個類初始化的時候被調用的
ClassCleanup() 是在類結束的時候,也就是一個類所有的case跑完的時候被調用的。
TestInitialize() 是在每個case跑之前被調用的。
TestCleanup() 是在每個case調用之后被調用的。
對么?粗體的這句話不對,其余是對的。
測試用例的運行是無序的,包含多個類的情況。
看下面測試用例的之情情況你就明白了:
AssemblyInitialize
TestClass1: ClassInitialize
TestClass1: TestInitialize
TestClass1: MyTestCase1
TestClass1: TestCleanup
TestClass2: ClassInitialize
TestClass2: TestInitialize
TestClass2: MyTestCase2
TestClass2: TestCleanup
TestClass1: ClassCleanup
TestClass2: ClassCleanup
AssemblyCleanup
ClassCleanup() 并不意味著TestClass1 的ClassCleanup 在這個類的最后一個case跑完之后被立即調用!事實上,它會等待所有case都被運行完之后,同TestClass2 的ClassCleanup 一塊執行。
具體原因看這個帖子,How to run ClassCleanup (MSTest) after each class with test?
三個Action
還是看下面的一個例子:
[TestMethod()]public void Test_GetBasicRevenueByName(){SqlDatabaseTestActions testActions = this.SqlTest1Data; ? ?? ? ?// Execute the pre-test script// System.Diagnostics.Trace.WriteLineIf((testActions.PretestAction != null), "Executing pre-test script...");SqlExecutionResult[] pretestResults = TestService.Execute(this.PrivilegedContext, this.PrivilegedContext, testActions.PretestAction); ? ?// Execute the test script// System.Diagnostics.Trace.WriteLineIf((testActions.TestAction != null), "Executing test script...");SqlExecutionResult[] testResults = TestService.Execute(this.ExecutionContext, this.PrivilegedContext, testActions.TestAction); ? ?// Execute the post-test script// System.Diagnostics.Trace.WriteLineIf((testActions.PosttestAction != null), "Executing post-test script...");SqlExecutionResult[] posttestResults = TestService.Execute(this.PrivilegedContext, this.PrivilegedContext, testActions.PosttestAction); }
每個測試用例中都會有三個action,這三個Action的用途如下:
PretestAction做的是測試前的準備工作,具體過程中可以為每個特定的case插入或更新測試需要的數據。
TestAction為調用存儲過程進行測試,將實際結果和預期結果進行對比。
PosttestAction做的是測試完成后的清理工作,這里可以對PretestAction中的插入或者更新的數據進行回滾,恢復初始環境。
最后的這個PosttestAction為我們的數據隔離提供了一種方法,所謂恢復初始環境的意思是執行一個case之前和之后數據庫中的數據完全一樣。
這里有個問題,在PretestAction中進行數據插入還比較好恢復,如果是刪除和更新呢?這就需要你記錄下刪除的和更新前的數據。太麻煩了。如果你的系統性能足夠好,或者對運行UT的時間沒有要求,可以用另外一種方法:restore DB。前面不是說過了么,我們在數據庫升級之后做了一個備份,我們在這里使用它。在什么地方執行restoreDB?對,在TestCleanup() 中進行。
[TestInitialize()]public void TestCleanup(){restoreDB(); }總結
具體的流程就說完了,總結一下:
準備數據庫
運行測試用例流程
數據清理的兩種方法
在PretestAction中添加數據恢復語句;
在TestCleanup()中restore DB。
實例
接下來我們從頭到尾演示一下用VS2013 + SQL Server 2012是如何做數據庫UT的。
創建一個簡單的數據庫DBUTDemo
創建兩張表。
創建一個存儲過程
join EmployeeBasicInfo bi on r.EmployeeNo = bi.EmployeeNo where bi.Name = @nameend
創建UT工程
點擊File->New->Project...
選擇Unit Test Project,輸入工程名,選擇創建路徑,點擊OK。
添加一個類
右鍵DBUTDemo->Add->New Item...
選擇SQL Server Unit Test,輸入名字,點擊Add。第一次添加數據庫測試類需要配置數據庫:
點擊New Connection。
輸入Server name,選擇我們剛才創建的數據庫DBUTDemo,點擊Test Connection。如果成功會彈出對話框。連續兩次點擊OK。數據庫配置就完成了。
創建三個Actions
點擊Click here to create來創建TestAction,點擊之后發現多了一個resx文件。
輸入下面的測試代碼:
declare @return_value ?int,@name ?nvarchar(50)EXEC ? ?@return_value = [dbo].[GetBasicRevenueByName]@name = N'three zhang'SELECT ?'Return Value' = @return_value接下來創建另外兩個Action:
分別輸入如下代碼:
insert into EmployeeBasicInfo values(1,'three zhang', ? ?'16625344257')insert into EmployeeBasicInfo values(2,'four li', ? '16625344258')insert into EmployeeBasicInfo values(3,'simon', '16625344259')insert into EmployeeBasicInfo values(4,'jack', ?'16625344250')insert into EmployeeRevenue values(1 ? ?,30000 ?,500 ? ?,20000)insert into EmployeeRevenue values(2 ? ?,28000 ?,500 ? ?,19000)insert into EmployeeRevenue values(3 ? ?,27000 ?,500 ? ?,10000)insert into EmployeeRevenue values(4 ? ?,26000 ?,500 ? ?,20000) delete from EmployeeRevenuedelete from EmployeeBasicInfo最后添加測試條件
我們添加了兩個測試條件,值可以在屬性界面中修改:
第一個測試條件是在返回結果集1中,第一行第二列的期望值為30000,也就是three zhang的基本工資為30000。
第二個測試條件測試結果集1非空。
編譯,運行
編譯成功后,打開Test Explorer,run我們剛才創建的case,測試通過。
Ordered Test
最后說下數據庫測試用例如果需要固定的順序該怎么辦,微軟提供了一種測試用例類型叫做Ordered Test:
這種case是把幾個case集合成為了一個,可以自己選擇需要運行的普通的case,自己指定順序。因為順序固定了,這些cases中使用的數據就是可控的,因此在一個ordered case中的幾個case可以共同使用某些數據,我們可以將數據隔離的單位由單個case變為幾個case甚至一個類中的所有cases。
原文地址:http://www.cnblogs.com/harlanc/p/7007145.html
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的谈一下我们是怎么做数据库单元测试(Database Unit Test)的的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .Net Core 图片文件上传下载
- 下一篇: Web前端知识体系精简