昨天一位園友sinodragon21在Windows phone應用開發[9]-單元測試評論中.提出關於Windows phone 單元測試中能否使用微軟的Pex自動化生成工具生成單元測試用例.和單元測試質量即代碼覆蓋率統計問題.很有價值.
針對這兩個問題.首先需要解釋.關於Windows phone 中單元測試現狀.針對Windows phone應用程序Unit Test 官方並沒有在IDE提供對應的測試框架,目前開發者社區使用比較廣泛框架是MS Windows phone 產品組Jeff.wilcox維護的Silverlight Unit Test Framework[SUTF] Windows phone版本.詳見Blog:Updated Silverlight Unit Test Framework bits for Windows Phone and Silverlight 3 . 但這個版本始終作為個人形式對開發者發布.並沒有以正式的官方渠道向Windows phone開發者推送,所以官方對此並沒有提供指定的維護和開發者社區支持.
至此官方也就沒有提供自動化測試工具道理. 類似Android 平台自動化Monkeyrunner之外還有更多選擇.而Windows phone自動化測試只能靠開發者自己想辦法.
那么關於SUTF 中Code Converage代碼覆蓋率呢? 針對這個問題 特別咨詢SUTF WP版本維護作者Jeff.Wilcox.得到回復信息如下:
HI Jeff,
how to take code coverage from SilverLight Unit test framework for WP7?
jeffwilcox Reply:
Code coverage is not available, it was a feature that was cut before
shipping for 2010 from the toolkit.
在2010年發布SUTF版本時已經去掉對Code Converage支持. so.既然如此是否真的就此定論了.? Now 現在提到Pex and Moles.還是不甘心.打算動手在應用程序中親自驗證.本篇並不打算立即采用Pex在SUTF進行驗證.有必要系統了解Pex and Moles.
<1>構建Pex測試環境
Well.關於Pex And Moles其實可以分為兩個部分.其一Pex-全稱是[Program EXploration]是微軟研究院的一個關於白盒測試自動生成工具.原來開發人員只能通過指定路徑編寫獨立的測試用例.PEX通過分析代碼來自動生成測試用例。對於程序里面的每一行代碼,PEX都會盡可能地生成合適的輸入值來達到提高覆蓋率的目標。同時PEX還會分析代碼中的分支,生成覆蓋更多分支的測試代碼(輸入數據);PEX在執行代碼的同時會監控和分析代碼的控制流和數據流,了解程序的行為。每運行完單一個測試以后,PEX會選擇一條在前面的測試中沒有覆蓋到的路徑,並且嘗試執行它.本篇主要來講到的是PEX.
PEX自動化生成用例並執行UT返回測試結果的過程.大大減少開發人員手動編寫大量測試用例情況發生.
關於PEX and Moles可通過如下鏈接了解更多:
PEX And Moles:
Pex and Moles - Isolation and White box Unit Testing for .NET
Download Page:
Pex and Moles – Download Page LinkGet Start Online PDF Document:
找到下載頁面.PEX現在有3個Flavor,支持2個版本的Visual Studio,分別是VSTS2010和VSTS2008,這3個版本的PEX在功能上存在區別,這主要因為下載受眾群體不同,一般用戶只能使用到PEX功能.本篇所有演示都是基於Visual Studio 2010. 基於MSDN訂閱權限下載PEX And Moles完整版本.
安裝完成后.可以打開Visua Studio 立即創建一個Console Application命名為BasicConsolePexComponent_Demo,在此先不要把焦點放在PEX原理實現上.來快速體驗一下PEX.首先需要在Program類中頂一個轉換字符串的方式.作為測試用例的宿主程序.創建如下:
1: /// <summary>
2: /// Test Case Convert String To Upper
3: /// </summary>
4: /// <param name="inputString">Input String</param>
5: /// <returns>Converted String</returns>
6: public string ConvertStringToUpper(string inputString)
7: {
8: string convertString = string.Empty;
9: if (!string.IsNullOrEmpty(inputString))
10: {
11: char[] convertCharArray = inputString.ToCharArray();
12: if (convertCharArray.Length > 0)
13: {
14: int count = 0;
15: foreach (char currentChar in convertCharArray)
16: {
17: if (count % 2 == 0)
18: convertString += currentChar;
19: }
20: }
21: }
22: return convertString;
23: }
well一般情況下.關於如何建立單元測試應用程序,一般如果沒有PEX之前.可能作為開發人員更多的是根據該方法實現.創建一個新的Test測試項目.構建對應測試用例.實現對該方法的UT.其實這個過程已經Visual Studio內置了這些工作已經自動創建.只需右鍵點擊就能看到Create Unit Test選項:
點擊后能發現.其實Create Unit TEst為了避免測試框架和測試用例污染源代碼本身.一般情況要對單元測試用例和源代碼執行分離.也就是創建一個新的單獨測試項目.
此時在執行測試之前需要做一些設置點擊Settings按鈕:
需要設置創建默認文件以及對應測試類和測試方法名稱.點擊ok.如果沒有設置對應測試項目名稱會如下提示:
修改完成后開始創建.創建過程可能會有如下提示:
創建前提示主要提到兩點.需要為測試項目BasiceConsolePexComponent_Demo設置InternalsVisible屬性可用.這是設置其實內在的原因是.對一個組件或模塊進行單元測試時.單元測試用例需要調用定義在測試組件或模塊中的Internal成員對象時. 需要跨程序集訪問.而Modifier的Internal類型成員默認設置僅限於當前程序集訪問.而對宿主程序而言需要暴露給測試程序集調用.
另外一點在很多情況下,我們需要將最終的程序集以強命名的形式發布。為此,我們修改Lib項目設置,開啟"Sign the assembly”開關,並創建一個密鑰文件.不能通過編譯,具體的錯誤信息為:“Friend assembly reference 'Test' is invalid. Strong-name signed assemblies must specify a public key in their InternalsVisibleTo declarations.”,針對需要指定的不是程序名的強命名,而是指定對程序集進行簽名時采用的公鑰.把公鑰指定到InternalsVisibleToAttribute特性中即可.
點擊ok.看一下項目結構:
多了一個測試項目和該項對應Program方法測試類.now構建好了測試項目.在回到宿主程序Program類中對字符串轉換操作的方法見一個UT.找到該方法名右鍵依然可見Create Unit Test選項點擊看到:
可以看到針對方法做UT時會把該UT的應用程序的輸出默認為剛才創建的測試項目:AutomationConsole.Test.點擊ok.找到測試項目下ProgramProgramTestHelper類可以看到增加一個測試方法如下:
1: /// <summary>
2: ///A test for ConvertStringToUpper
3: ///</summary>
4: [TestMethod()]
5: public void ConvertStringToUpperTestConvertString_Test1()
6: {
7: Program target = new Program(); // TODO: Initialize to an appropriate value
8: string inputString = string.Empty; // TODO: Initialize to an appropriate value
9: string expected = string.Empty; // TODO: Initialize to an appropriate value
10: string actual;
11: actual = target.ConvertStringToUpper(inputString);
12: Assert.AreEqual(expected, actual);
13: Assert.Inconclusive("Verify the correctness of this test method.");
14: }
可以看到該測試方法命名依然采用默認命名方式.方法內代碼實現關於調用Program類中字符串轉換大寫操作的方法ConvertStringToUpper. 分別定義三個變量的字.其實從變量名稱完全可以看出這是一個默認的測試用例.要想改UT達到想做.只需修改對應變量參數的值來匹配.InputString作為方法參數輸入.經過測試用例會會生成對應期望值和方法執行實際值.最后通過對期望值和實際比對獲得對應測試結果.直接運行 可以看到測試結果如下:
well因為兩個測試用例都沒做了修改沒有實際值傳入所以都是Inconcluetive測試結果不確定.現在看看這種傳統方式下一Visual Studio Unit TEst框架為主UT編寫開發人員要重復做哪些工作.?首先要重復手動編寫大量的UT測試用例.目的是在給定的方法中為每個執行路徑手寫獨立測試. 而且針對這種無法避免的要創建一個獨立的測試項目.其實在實際編碼中.並不是所有模塊都需要即時建立UT.可能需求發生變更.從開發人員角度來說創建一個笨重測試項目這意味更多精力發在測試代碼維護上.可能我們更需要一種既能夠創建對應測試項目同時也能即時看到當前版本Code UT效果.
Now.UT屬於白盒測試的范疇.如果能夠有一個很好白盒測試工具能夠封裝這些重復的行為.並能夠重復執行UT測試用例.讓開發人員從Ut的過多細節和維護中解放出來關注更核心的業務邏輯.Pex and moles就是這樣的一個工具.
<2>Pex 構建白盒測試
雖然本篇幅沒有核心焦點放到Pex實現原理上.但關於Pex and Moles其實分為兩塊整理官方原文翻譯如下[直譯]:
Pex Pex 是一個 Visual Studio 外接程序,用於補充 .NET Framework 應用程序上的單元測試。 Pex 在 Visual Studio 代碼編輯器中查找對應程序方法的輸入和輸出值。 PEx將這些值另存為將具有高代碼覆蓋率的小型測試套件。
Moles Moles 允許開發人員將任何 .NET 方法替換為委托。 Moles 通過使用 Detour 和 Stub 提供隔離來支持單元測試。 因為 Moles 在方法級別工作,所以當目標 API 不支持它時,它提供替代項進行模擬。 SharePoint 是一個受益於隔離的常見 API 示例,但不直接支持模擬。Moles 還可用於錯誤植入,因為它使得開發人員在測試下向代碼中注入任意行為變得輕松.
Pex核心是給開發人員一個手寫的參數化單元測試,Pex完全自動地分析代碼,來決定相關的測試輸入。其結果就是生成一個有着高度代碼覆蓋的傳統單元測試,另外,Pex還會建議開發人員如何去修復所發現的Bug.
安裝完Pex and Moles工具后.打開Visual Studio 可以看到右鍵選項中多了:
Run Pex 和Pex 下Create Parameterized Unit Tests兩個可操作選項.針對Pex測試效果.首先在Programe類添加一個字符串合並操作方法代碼如下:
1: /// <summary>
2: /// Test Case Spilt String And Merge Operator
3: /// </summary>
4: /// <param name="firstname">First Name</param>
5: /// <param name="lastname">Last Name</param>
6: /// <returns>Merge String</returns>
7: public string SpiltStringToMerge(string firstname, string lastname)
8: {
9: string mergeString = string.Empty;
10: char[] firstNameArray = firstname.ToCharArray();
11: char[] lastNameArray = lastname.ToCharArray();
12: int count = 0;
13: foreach (char currentChar in firstNameArray)
14: {
15: if (count % 2 == 0)
16: mergeString += currentChar + lastNameArray[count];
17: }
18: return mergeString;
19: }
注意該方法的輸入參數firstname和lastname在方法內都沒有判斷是否空字符串或是Null而且里面涉及字符數組的操作.針對該方法采用Pex方式執行UT操作.右鍵調集選中Run Pex 提示如下:
注意我們當前類是在Program類下.該類的簽名如下:
1: class Program
2: {
3: }
提示的意思當前類型不可見.無法通過Pex方式創建UT,需要把當前類的簽名改成公開 虛類型即可:
1: public partial class Program
2: {
3: }
在來運行Run Pex 彈出執行 結果窗體如下:
可以看到Pex在沒有創建一個獨立單元測試項目和對應測試類情況.自動對當前方法進行白盒測試封裝.運行測試109 Runs次.創建了7個測試用例. PEx會監視當前類,並且運行這個方法.執行多次測試.其中有三次測試失敗的.並Sunmmary/Exception 提出具體測試參數和出現的異常信息.
點擊對應失敗的測試用例.可以在右側彈出對應異常堆棧詳細信息.:
點擊DEtails可以看到執行該測試用例的代碼和對應異常信息:
在查看異常詳情右側窗口中.可以執行如下操作:
Save Test保存當前PEX的測試用例為普通單元測試用例.點擊保存:
如果在此之前沒有創建對應Pex測試項目會提示當前測試用例輸出.或是提示創建一個新的測試項目:
上面Project Under Test是待測試的宿主項目.而下面是需要創建新的測試項目設置.點擊OK創建一個新的測試項目.這里在RunPex時並沒有強制開發人員強制建立一個新的測試項目.但是這里SAve 目的是在於如果覺得當前測試代碼可以留存做以后回歸測試.PEX也是支持測試代碼保存的.
add Precondition是用來通過Pex自動添加代碼來避免錯誤的發生.而All Exception則允許次異常在當前應用程序中出現.send to如果你的項目采用TFS版本控制.sendto操作會吧所有信息發到文本或剪切板中.這里所有的信息包括Detail和STack Trace 如果TFS里配置WorkItem,PEX會自動發送對應的WorkItem中.
well現在既然存在異常.通過Add PreCondition來通過Pex來修正.會看到如下菜單:
這個窗體中上半部分告訴開發人員PEX在當前會做哪些操作.操作會修改哪些方法等.下半部分則是PEX具體的添加的修復代碼.可以看到對firstname輸入參數因當前NullException異常所以添加了是否非Null的判斷。點擊Apply可以看到原來代碼中添加一段判斷代碼 完整如下:
1: public string SpiltStringToMerge(string firstname, string lastname)
2: {
3: // <pex>
4: if (firstname == (string)null)
5: throw new ArgumentNullException("firstname");
6: // </pex>
7: string mergeString = string.Empty;
8: char[] firstNameArray = firstname.ToCharArray();
9: char[] lastNameArray = lastname.ToCharArray();
10: int count = 0;
11: foreach (char currentChar in firstNameArray)
12: {
13: if (count % 2 == 0)
14: mergeString += currentChar + lastNameArray[count];
15: }
16: return mergeString;
17: }
<pex>標簽之間注釋就是PEX修復是添加的代碼.如果覺得PEX代碼不夠美化.開發人員也完全可以手工修改.自此你會看到PEX幫開發人員找到一個Bug並嘗試不在維護UT代碼的情況下並修復該異常.well.當然如上演示只是PEX作為白盒測試工具的豐富功能一個特性.其實剛開始看官方給出解釋最吸引我莫過於它支持寫參數化單元測試[Parameterized Unit Test].方式.
<3>Pex參數化單元測試
在Pex中針對白盒測試提出參數化單元測試的概念-Parameterized Unit Test。Dang我們通過PEX實現更多模塊的UT操作.確保所有單個模塊能夠按照程序設計預期執行. 其實實際效果並非如此.PEX通過一個指令一個指令地分析.NET代碼,解釋代碼執行時的動作,然后“以一種完全自動的方式,計算出那些能觸發邊角代碼的相關測試輸入。”
PEX在執行和分析代碼行為時.只是得到代碼最基本代碼模塊行為簡單異常類似NullException.但是實際上開發人員並沒有寫過一個關於所有模塊集成起來之后完整測試用例. 無法預期當前軟件的行為.PEX提供參數化的單元測試提供一種強大的方式來評估當前代碼在集成后是否如預期一樣行為.
找到上次增加SpiltStringToMerge字符串合並操作方法做實例.創建參數化單元測試:
找到該方法選中右鍵點擊可以看到Pex->Create Parameterized Unit Tests選項:
如果原來沒有創建對應的PEX測試項目會提示創建一個新的Test Project.與宿主程序分離.可以看到增對測試文件目標PEX增加多重過濾項. 在Test Project項目輸出上如果沒有創建Test Project則創建.當然針對創建的測試項目可以指定不同的測試框架.PEX中集成了NUnit/Visual Studio Unit TEst/MbUnit V系列等.讓開發人員以自己最熟悉的測試框架來維護測試代碼.Pex還基於擴展反射可管理子協議API (Extended Reflection managed profiling API)對監測應用程序的集成提供了支持.在創建看一下設置項Settings:
可以設置默認創建的命名空間和測試類下默認PexMethod測試方法.這里采用默認方式創建:
創建成功后Solution目錄:
打開ProgramTest.cs文件可以看到這個PEX自動生成的參數化單元測試方法:
1: [PexClass(typeof(Program))]
2: [PexAllowedExceptionFromTypeUnderTest(typeof(InvalidOperationException))]
3: [PexAllowedExceptionFromTypeUnderTest(typeof(ArgumentException), AcceptExceptionSubtypes = true)]
4: [TestClass]
5: public partial class ProgramTest
6: {
7: /// <summary>Test stub for SpiltStringToMerge(String, String)</summary>
8: [PexMethod]
9: public string SpiltStringToMerge(
10: [PexAssumeUnderTest]Program target,
11: string firstname,
12: string lastname
13: )
14: {
15: string result = target.SpiltStringToMerge(firstname, lastname);
16: return result;
17: // TODO: add assertions to method ProgramTest.SpiltStringToMerge(Program, String, String)
18: }
19: }
其實關於PEX參數化單元測試的概念.其實它就是一個帶有參數並通過調用宿主測試程序簡單測試方法.其實這里很多人都很疑惑怎么和傳統創建單元測試方法和PEX參數化方法區別在哪呢?其實昨天我在看到這時也很困惑很久.后來在官方找了很多資料.總算是找到一點權威的論述:
A unit test is a method without parameters represents a test case which typically executes. a method of a class-under-test with fixed arguments and verifies that it returns the.
expected result. A parameterized unit test (PUT) is the straightforward generalization。of a unit test by allowing parameters. PUTs make statements about the code’s behavior.
for an entire set of possible input values, instead of just a single exemplary input value.
相對傳統創建的單元測試一般情況參數都是指定固定的參數值.並在執行后會預期函數中返回對應的結果.PEX參數化單元測試概念則不同.在PEX中單元測試的參數可以輸入任何可能的參數用來驗證程序代碼執行中所有可能的行為.你大概明白PEX提出這個概念的目的了吧.也就是說PEX在執行的時候會分析PEX參數化單元測試代碼方式來查看宿主程序代碼的行為.當然這個過程需要集成宿主程序的代碼.而相對於傳統固定參數值.PEX單元測試參數可以是任意的.這樣更便於測試過程中測試宿主程序代碼可能出現崩潰的幾率和行為.
當前已經創建PEX對應的測試項目.在回到宿主橫須Program類中.再次運行Run PEX:
出了能看到原來Pex Test Results窗體測試結果.注意這時PEX內部根據測試需要會在測試項目對應測試ProgramTEst類中.創建普通測試方法[TestMethod]同時也會創建PEX下參數化單元測試. 創建普通測試的數量和測試結果中測試用例的數量是一致的.如果TestReaults.PEX使用7個用例那么在ProgramTest.SpiltStringToMerme.g.cs文件中肯定會有相同數量的普通測試方法存在.PEX參數化單元測試方法則放在ProgramTest.cs文件中.
如果你覺得這個過程不明顯.或是想了解一些PEX工作處理過程.可以選中右鍵可以PEX->Delete Generated Unit Tests操作:
其實該操作的目的會自動刪除ProgramTest.SpiltStringToMerme.g.cs文件中所有的普通測試方法[TestMethod]也就是當前使用測試用例.然后找到該方法在運行Run PEX./運行玩后可以找到對應測試實例的Save Test方式在把指定的測試用例保存到ProgramTest.SpiltStringToMerme.g.cs文件中.well 這里保存第一個通過的測試用例 低級Save TEst:
Apply.完成后.會看到ProgramTest.SpiltStringToMerme.g.cs文件中多了一個普通測試方法.同時在Test Result窗體中發現每個測試的圖標不再包含一個磁盤位圖而是一個對號.:
1: public partial class ProgramTestTest
2: {
3: [TestMethod]
4: [PexGeneratedBy(typeof(ProgramTestTest))]
5: public void SpiltStringToMerge355()
6: {
7: string s;
8: ProgramTest s0 = new ProgramTest();
9: Program s1 = new Program();
10: s = this.SpiltStringToMerge(s0, s1, "", "");
11: Assert.AreEqual<string>("", s);
12: Assert.IsNotNull((object)s0);
13: }
14: }
這樣有選擇的方式讓開發人員很輕松能夠保存在不同版本需求變動生成的測試用例/.而且是可選擇的.當然在添加過程往往會提示一些命名上問題例如如下提示:
這是因為現在PEX現在對中文GB2312格式支持存在亂碼的問題. 當然在創建是PEX為了防止訪問路徑過長.就限制在PEX所有文件命名長度.
其實到這里很多人依然對PEX 參數化單元測試的概念依然有些困惑.這個概念其實很簡單.但在實際運用你會發現它的實際用途的好處.如果你熟悉並常用的單元測試.但還不理解PEX中參數化單元測試概念.可以從以下幾個方向去考慮[僅作參考]:
理解參數化單元測試的方向:
[1]:當時你手寫普通的單元測試.思考以下你要測試代碼的行為.在構建單元測試時你的需要多少個獨立參數傳入.?
[2]:目前尚不清楚確切的數字可能會是什么.但在測試過程它是很重要的,因PEX角度來說開發人員永遠有時間來即時創建和維護測試代碼.
當然更多還請參考官方的資料:http://research.microsoft.com/en-us/projects/pex/default.aspx
<4>Pex小結
本篇PEX 自身應用角度來闡述PEX相對傳統單元測試所具有的特點.作為一個白盒的測試工具.開發人員常在程序開發過程中.需求往往也是不斷變化的.當然也無法避免未來開發過程中.或進入代碼維護階段是.所需要面對代碼修正的問題.為了每次都能保證軟件代碼工作都正確無誤.而且即時在需求發生變更需要添加新功能時.也能保證原有功能正常運行.使用可控的測試導向的開發模式是必行的.
當然作為開發人員要在開發過程手工編寫大量的測試用例並維護笨重測試的代碼.這會在一定程度上導致項目開發進度延緩。這也是很多開發人員忽視UT一個原因之一.但現在PEX白盒測試工具.可以把測試流程完全的自動化.把開發人員從單元測試的需求中解脫出來專注於核心業務開發之上.
本篇並沒有急於在Windows phone Application 中驗證PEX是否可行.但是PEX透露出來關於測試理念確實很有深入的價值.稍后會在下篇副中無論成功還是失敗.演示Windows phone應用程序中使用PEX整個過程.
如有問題請在評論中提出.本篇所有源代碼下載地址如下:/Files/chenkai/BasicConsolePexComponent_Demo.rar