《軟件測試自動化之道》讀書筆記 之 SQL 存儲過程測試
2014-09-28
待測程序
測試程序
創建測試用例以及測試結果存儲
執行T-SQL腳本
使用BCP工具導入測試用例數據
創建T-SQL 測試套件
當待測存儲過程返回行集的時候,如何判斷測試結果是否通過
當待測存儲過程返回out參數時,如何判斷測試結果是否通過
當待測存儲過程沒有返回值時,如何判斷測試結果是否通過
許多基於Windows的系統都使用了SQL Server作為后台組件。待測程序經常通過存儲過程來訪問數據庫。對於這些應用場景,可以把SQL存儲過程當成應用程序的輔助函數。有兩種方法可以用來編寫對SQL存儲過程的自動化測試:
- 測試套件代碼用T-SQL語言來編寫,在類似於查詢分析器(Query Analyzer)或者Management Studio這樣的程序里執行
- 測試套件代碼用.NET語言連編寫
待測程序
下面代碼創建數據庫‘dbEmployees’;創建表‘talEmployee’,插入相應數據;創建存儲過程‘usp_HireAfter’,創建登陸用戶‘employeesLogin’並賦予訪問數據庫和存儲過程的權限:
makeDbEmployees.sql:

1 -- Database setup: makeDbEmployees.sql 2 3 use master 4 go 5 6 if exists (select * from sysdatabases where name='dbEmployees') 7 drop database dbEmployees 8 go 9 10 if exists (select * from syslogins where name = 'employeesLogin') 11 exec sp_droplogin 'employeesLogin' 12 go 13 14 create database dbEmployees 15 go 16 17 use dbEmployees 18 go 19 20 create table tblEmployees 21 ( 22 empID char(3) primary key, 23 empLast varchar(35) not null, 24 empDOH datetime not null, 25 ) 26 go 27 28 -- this is dev data, not test case data 29 insert into tblEmployees values('e11','Adams', '06/15/1998') 30 insert into tblEmployees values('e22','Baker', '06/15/2001') 31 go 32 33 exec sp_addlogin 'employeesLogin', 'September,2014' 34 go 35 exec sp_grantdbaccess 'employeesLogin' 36 go 37 38 create procedure usp_HiredAfter 39 @dt datetime 40 as 41 select * from tblEmployees where empDOH > @dt 42 go 43 44 grant execute on usp_HiredAfter to employeesLogin 45 go 46 47 -- end script
注意:當測試SQL存儲過程時,有兩個理由使你最好不要使用用於開發的數據庫進行測試:
- 測試存儲過程有時會修改包含這個存儲過程的數據庫
- 開發的那個數據庫通常沒有足夠多的數據或者這些數據不是為了測試的目的而設計的
SQL數據庫支持兩種不同的安全模式:使用Windows認證可通過Windows賬號ID和密碼連接數據庫,使用混合模式認證可以通過SQL登陸ID和SQL密碼來連接數據庫。若想通過SQL認證來連接測數據庫,應該使用系統存儲過程sp_addlogin()創建SQL登陸賬號和密碼。
SQL登陸賬號和SQL用戶區別:
- SQL登陸賬號是服務器范圍的對象,它用來控制針對裝有SQL Server的機器的連接許可;
- SQL用戶是數據庫方位的對象,他用來控制數據庫以及他所包含的表、存儲過程和其他一些對象的權限許可。
當為一個SQL登陸賬號分配權限的時候,會自動創建一個名為SQL用戶。所以最終會有一個SQL登陸賬號和一個SQL用戶,兩個名字相同且相互關聯。當然也可以讓不同名字的登陸賬號和用戶相互關聯。
測試程序
創建測試用例以及測試結果存儲
以下T-SQL腳本,創建一個數據庫然后創建一些表用來保存測試用例的輸入數據和測試結果;創建一個專用SQL登陸賬號,賦予一定權限:
makeDbTestCasesAndResults.sql:

1 -- Test case data and results setup: makeDbTestCasesAndResults.sql 2 3 use master 4 go 5 6 if exists (select * from sysdatabases where name='dbTestCasesAndResults') 7 drop database dbTestCasesAndResults 8 go 9 10 if exists (select * from syslogins where name = 'testLogin') 11 exec sp_droplogin 'testLogin' 12 go 13 14 create database dbTestCasesAndResults 15 go 16 17 use dbTestCasesAndResults 18 go 19 20 create table tblTestCases 21 ( 22 caseID char(4) primary key, 23 input datetime not null, 24 expectedChecksum int not null 25 ) 26 go 27 28 -- this is the test case data for usp_HiredAfter using a checksum expected 29 -- value approach 30 -- can also read from a text file using BCP, DTS, or a C# program 31 insert into tblTestCases values('0001','01/01/1998', 1042032) 32 insert into tblTestCases values('0002','01/01/1998', 9999999) -- deliberate error 33 insert into tblTestCases values('0003','01/01/2000', 25527856) 34 insert into tblTestCases values('0004','01/01/2006', 0) 35 go 36 37 create table tblResults 38 ( 39 caseID char(4) not null, 40 result char(4) null, 41 whenRun datetime not null 42 ) 43 go 44 45 exec sp_addlogin 'testLogin', 'September,2014' 46 go 47 exec sp_grantdbaccess 'testLogin' 48 go 49 50 grant select, insert, delete, update on tblTestCases to testLogin 51 go 52 53 grant select, insert, delete, update on tblResults to testLogin 54 go 55 56 -- end script
執行T-SQL腳本
運行T-SQL腳本,有好幾種方法:
- 使用查詢分析器;
- 使用osql.exe;
- 使用批處理(BAT)
下面使用osql.exe程序使用以下命令執行這個腳本:
osql.exe -S(local) -U loginID -P loginPassword -i makeDbTestCasesAndResults.sql -n > RESULT.txt
或
osql.exe -S(local) -E -i makeDbTestCasesAndResults.sql -n > RESULT.txt
-E表示使用Windows認證模式。
注意:osql.exe的參數是大小寫敏感的。
使用BCP工具導入測試用例數據
創建一個BCP格式的文件,用於把你想要導入的文本文件信息映射到目標SQL表中。然后把上述格式的文件作為參數傳給命令行工具bcp.exe
Step 1: 先看一下如何從表‘tblTestCases’中導出數據
create table 'tblTestCases'的腳本:

1 create table tblTestCases 2 ( 3 caseID char(4) primary key, 4 input datetime not null, 5 expectedChecksum int not null 6 )
用BCP導出表‘tblTestCases’到dat文件中:
bcp dbTestCasesAndResults.dbo.tblTestCases out C:\Code\AutomationTest\newData.dat -c -T
圖1導出的數據
Step 2: 用BCP導出格式文件
EXEC master..xp_cmdshell 'bcp dbTestCasesAndResults.dbo.tblTestCases format nul -f C:\Code\AutomationTest\newData.fmt -c -T'
newData.fmt內容:

1 11.0 2 3 3 1 SQLCHAR 0 4 "\t" 1 caseID SQL_Latin1_General_CP1_CI_AS 4 2 SQLCHAR 0 24 "\t" 2 input "" 5 3 SQLCHAR 0 12 "\r\n" 3 expectedChecksum ""
上述內容中,
- 11.0是SQL Server2012的版本號;
- 3表示從第3行以后的內容是映射信息。
- 每個映射行有8列,前5列代表於輸入數據有關的信息(本例中,指newData.dat),后3個列代表要導入的目標信息(本例中,指SQL表):
- 第一列其實是從第一列開始的一系列數字
- 第二列是要導入的數據的類型(當從文本文件導入時,不過什么值,都是SQLCHAR)
- 第三列是前綴長度(prefix length)
- 第四列是輸入字段字符的最大長度
- 第五列是分隔符
- 第六列第七列分別指SQL表里相應列的順序和名稱
- 第八列指SQL排序規則(SQL_Latin1_General_CP1_CI_ASSQL默認排序規則)
Step3: 修改newData.dat,導入
修改后newData.dat內容:
000 2007-01-01 00:00:00.000 7
用BCP命令導入:

1 bcp.exe dbTestCasesAndResults.dbo.tblTestCases in newData.dat -f newData.fmt -S. -T
創建T-SQL 測試套件
使用SQL游標(cursor)遍歷這個測試用例數據表。針對每個測試用例,調用待測存儲過程並且取得它的返回值。把它的實際值於期望值進行比較,判定結果,保存測試結果。
SQL游標設計用來處理單個的數據行並不向其他SQL操作那個處理行集(rowset)。
首先聲明一個指向保存測試數據的SQL表的游標

1 declare tCursor cursor fast_forward 2 for select caseID, input, expectedChecksum 3 from dbTestCasesAndResults.dbo.tblTestCases 4 order by caseID
注意:游標於其它SQL變量不同,游標變量的名字前面並沒有@字符。可供聲明游標的有好幾種。FAST_FORWARD最適合用來讀取測試用例數據,它實際上就是FORWAR_ONLY和READ_ONLY的別名。
在使用游標前,必須先打開游標。然后,如果想要遍歷整個數據庫表,則必須通過fetch next語句於都數據庫表的第一行:

1 open tCursor 2 fetch next 3 from tCursor 4 into @caseID, @input, @expectedChecksum
對第一行進行預讀是為了對下面的循環進行控制,我們使用變量@@fetch_status來控制用於讀取的這個循環,這個變量表示最近一次fetch操作的狀態。如果操作成功,@@fetch_status值為0;若失敗,@@fetch_status值為-1,-2。因此,可向下面這樣遍歷整個數據庫表:

1 while @@fetch_status = 0 2 begin 3 4 --運行測試用例 5 6 fetch next 7 from tCursor 8 into @caseID, @input, @expectedChecksum 9 end
在主循環內部,我們需要調用待測存儲過程,並且把測試用力輸入數據傳給它。去會的值打印出來:
(注意:下面腳本調用存儲過程‘dbEmployees.dbo.usp_HiredAfter時,這里假設它只返回單個值)

1 exec @actual = dbEmployees.dbo.usp_HiredAfter @input 2 3 if(@actual=@expected) 4 begin 5 set @resultLine=@caseID + ': Pass' 6 print @resultLine 7 end 8 else 9 begin 10 set @resultLine=@caseID + ': Fail' 11 print @resultLine 12 end
使用完一個SQL游標之后,必須關閉這個游標並且調用deallocate命令把它作為一個資源釋放:

1 close tCursor 2 deallocate tCursor
SQLspTest.sql測試腳本:

1 -- =========================================================== 2 -- TestAuto.sql 3 4 truncate table dbEmployees.dbo.tblEmployees 5 6 insert into dbEmployees.dbo.tblEmployees 7 values('e11','Adams', '06/15/1998') 8 insert into dbEmployees.dbo.tblEmployees 9 values('e22','Baker', '06/15/2001') 10 insert into dbEmployees.dbo.tblEmployees 11 values('e33','Young', '06/15/1998') 12 insert into dbEmployees.dbo.tblEmployees 13 values('e44','Zetta', '06/15/2001') 14 -- other data would be inserted too 15 16 17 declare tCursor cursor fast_forward 18 for select caseID, input, expectedChecksum 19 from dbTestCasesAndResults.dbo.tblTestCases 20 order by caseID 21 22 declare @caseID char(4), @input datetime, @expectedChecksum int 23 declare @whenRun datetime 24 declare @actualChecksum int 25 declare @resultLine varchar(50) 26 27 set @whenRun = getdate() 28 29 open tCursor 30 fetch next 31 from tCursor 32 into @caseID, @input, @expectedChecksum 33 34 while @@fetch_status = 0 35 begin 36 37 exec @actualChecksum=dbEmployees.dbo.usp_HiredAfter @input 38 39 if (@actualChecksum = @expectedChecksum) 40 begin 41 set @resultLine = @caseID + ' Pass' 42 print @resultLine 43 insert into dbTestCasesAndResults.dbo.tblResults values(@caseID, 'Pass', @whenRun) 44 end 45 else 46 begin 47 set @resultLine = @caseID + ' Fail' 48 print @resultLine 49 insert into dbTestCasesAndResults.dbo.tblResults values(@caseID, 'Fail', @whenRun) 50 end 51 52 fetch next 53 from tCursor 54 into @caseID, @input, @expectedChecksum 55 56 end 57 58 close tCursor 59 deallocate tCursor 60 -- end script
當待測存儲過程返回行集的時候,如何判斷測試結果是否通過
待測存儲過程腳本如下:

1 create procedure usp_HiredAfter 2 @dt datetime 3 as 4 select * from tblEmployees where empDOH > @dt
首先,應該創建一個臨時表,用於保存存儲過程返回的QL行集:

1 create table #resultRowset 2 ( 3 empID char(3) primary key, 4 empLast varchar(35) not null, 5 empDOH datetime not null, 6 )
然后,我們可以調用待測存儲過程並把返回的行集存入臨時表:

1 insert #resultRowset (empID, empLast, empDOH) -- call sp under test 2 exec dbEmployees.dbo.usp_HiredAfter @input
接下來,我們計算臨時表的聚合校驗,並把實際值和期望值進行比較:

if (@@rowcount = 0) set @actualChecksum =0 else select @actualChecksum = checksum_agg(binary_checksum(*)) from #resultRowset if (@actualChecksum = @expectedChecksum) print 'Pass' else print 'Fail'
上面腳本中,內建的binary_checksum()函數返回表里的一行的校驗和。checksum_agg()函數返回一組值的聚合校驗和。這是待測存儲過程返回行集的時候,判斷測試是否通過的一種方法。
示例‘SQLspTest.sql’腳本:

1 -- =========================================================== 2 -- Test automation harness: SQLspTest.sql 3 -- test dbEmployees..usp_HiredAfter 4 -- reads test case data and writes results 5 -- to dbTestCasesAndResults 6 7 set nocount on 8 9 if not exists 10 (select * from master.dbo.sysdatabases where name='dbTestCasesAndResults') 11 raiserror('Fatal error: dbTestCasesAndResults not found', 16, 1) 12 go 13 14 if exists (select * from sysobjects where name='tap_Reset') 15 drop procedure tap_Reset 16 go 17 18 create procedure tap_Reset 19 as 20 truncate table dbEmployees.dbo.tblEmployees 21 22 insert into dbEmployees.dbo.tblEmployees 23 values('e11','Adams', '06/15/1998') 24 insert into dbEmployees.dbo.tblEmployees 25 values('e22','Baker', '06/15/2001') 26 insert into dbEmployees.dbo.tblEmployees 27 values('e33','Young', '06/15/1998') 28 insert into dbEmployees.dbo.tblEmployees 29 values('e44','Zetta', '06/15/2001') 30 -- other data would be inserted too 31 go 32 33 -- prepare dbEmployees with rich data 34 exec tap_Reset 35 go 36 37 declare tCursor cursor fast_forward 38 for select caseID, input, expectedChecksum 39 from dbTestCasesAndResults.dbo.tblTestCases 40 order by caseID 41 42 declare @caseID char(4), @input datetime, @expectedChecksum int 43 declare @whenRun datetime 44 declare @resultMsg varchar(80) 45 declare @actualChecksum int 46 47 create table #resultRowset -- for checksum technique 48 ( 49 empID char(3) primary key, 50 empLast varchar(35) not null, 51 empDOH datetime not null, 52 ) 53 54 set @whenRun = getdate() 55 56 print 'Stored procedure under test = usp_HiredAfter' 57 print ' ' 58 print 'CaseID Input Expected Actual Result' 59 print '===============================================' 60 61 open tCursor 62 fetch next 63 from tCursor 64 into @caseID, @input, @expectedChecksum 65 66 while @@fetch_status = 0 67 begin 68 69 exec tap_Reset -- reset test bed data 70 71 truncate table #resultRowset -- empty out the result rowset 72 73 insert #resultRowset (empID, empLast, empDOH) -- call sp under test 74 exec dbEmployees.dbo.usp_HiredAfter @input 75 76 if (@@rowcount = 0) 77 set @actualChecksum = 0 78 else 79 select @actualChecksum = checksum_agg(binary_checksum(*)) from #resultRowset 80 81 if (@actualChecksum = @expectedChecksum) 82 begin 83 set @resultMsg = @caseID + ' ' + cast(@input as varchar(11)) + 84 ' ' + cast(@expectedChecksum as varchar(20)) + ' ' + 85 cast(@actualChecksum as varchar(20)) + ' Pass' 86 print @resultMsg 87 insert into dbTestCasesAndResults.dbo.tblResults values(@caseID, 'Pass', 88 @whenRun) 89 end 90 else 91 begin 92 set @resultMsg = @caseID + ' ' + cast(@input as varchar(11)) + 93 ' ' + cast(@expectedChecksum as varchar(20)) + ' ' + 94 cast(@actualChecksum as varchar(20)) + ' FAIL' 95 print @resultMsg 96 insert into dbTestCasesAndResults.dbo.tblResults values(@caseID, 'FAIL', 97 @whenRun) 98 end 99 100 fetch next 101 from tCursor 102 into @caseID, @input, @expectedChecksum 103 104 end 105 106 close tCursor 107 deallocate tCursor 108 109 drop table #resultRowset 110 111 -- end script
當待測存儲過程返回out參數時,如何判斷測試結果是否通過
待測存儲過程腳本如下:

1 create procedure usp_GetLast 2 @empID char(3) 3 @empLast varchar(35) out 4 as 5 select @empLast =empLast from tblEmployees where empID = @empID 6 return @@rowcount
測試這個存儲過程的腳本如下:

1 declare @input char(3) 2 declare @empLat varchar(35) 3 declare @retval int 4 5 declare @expectedLast varchar(35) 6 declare @expectedRet int 7 8 set @input = 'e22' 9 set @expectedLast = 'Baker' 10 set @expectedRet=1 11 12 exec @retval =dbEmployees.dbo.usp_GetLast @input, @empLat out 13 if(@retval=@expectedRet and @empLat = @expectedLast) 14 print 'Pass' 15 else 16 print 'Fail'
注解:
SQL存儲過程有一個常用的設計模式,即存儲過程可以通過參數返回一個或多個值。當存儲過程返回的值不是int類型時,這個模式是必須的,因為return關鍵字只接受int類型的變量。
當待測存儲過程沒有返回值時,如何判斷測試結果是否通過
待測存儲過程腳本如下:

1 create procedure usp_DeleteEmployee 2 @empID char(3) 3 as 4 delete from dbEmployees.dbo.tblEmployees where empID=@empID
測試這個存儲過程的腳本如下:

1 declare @input char(3) 2 3 declare @expected int 4 declare @actual int 5 6 set @input = 'e22' 7 set @expected = 150847775 8 9 exec dbEmployees.dbo.usp_DeleteEmployee @input 10 select @actual=checksum_agg(checksum(*)) from dbEmployees.dbo.tblEmployees 11 if(@actual=@expected) 12 print 'Pass' 13 else 14 print 'Fail'
參考
[1] bcp命令詳解