密切注意Hardcoded路徑
使用單元測試向導在一個新的項目上添加測試是一件非常容易的事情。這個便利的特點可以節省你數百個小時的打字時間(微軟的開發者為此應收取很多費用)。但是,一些事情在幕后發生了,并且導致你很悲傷:路徑是hardcoded!這是一個問題,例如,當你移動一個測試到另一臺機器或是目錄時就會發生。我希望一個Visual Studio 2005的Service Pack允許你為測試設置一個開始路徑,hardcoded路徑將使用相對路徑。目前,但是解決hardcoded路徑的容易方式就是在團隊中有一個慈善的獨裁者,它總是判斷和頒布所有的驅動和路徑。如果它是一個小的項目,這種解決方案是很好的。另一個,一些實用的工作區在你的路徑中將引用% testdeploymentdir%環境變量。當測試運行時它被設置。
Hardcoded路徑出現的第一個地方就是在VSMDI文件中,這個文件是一個大的包裝皮里面包含簡單的測試列表。當你打開一個VSMDI文件,然后它不能找到測試集或者TESTRUNCONFIG文件,你將被提示為這些項目指出位置。我發現有意思的事情是下一次我再打開相同的VSMDI文件時,它會發現所有的東西。很明顯,更新的路徑一定存儲再某個地方,但是VSMDI文件本身并沒有被改變。我再VSMDI文件駐留的目錄中發現一個隱藏文件,名稱為 name.VSMDI.OPTIONS。
當我打開VSMDI.OPTIONS文件時(它是一個常見的XML文件),我搖晃我的頭,感覺受挫一樣。就像你在Figure 1中看到的一樣,在VSMDI文件中很明顯的有路徑搜索的支持,但是在Visual Studio用戶界面中沒有一種方式可以設置搜索的路徑。(此外,對這些VSMDI文件來說沒有理由被隱藏啊。)所以,當我想在一個不同的目錄結構中使用 VSMDI文件時,首先復制一個示例的VSMDI.OPTIONS文件到適當的目錄,然后手動的編輯它,在程序集和TESTRUNCONFIG文件中添加路徑。為這一部分的源代碼,我已經在BaseVSMDIOPTIONSFile文件夾中示例出了一個文件。
使用VSMDI文件主要的原因就是你可以指定要運行的測試列表-在動態開發過程中它是非常有用的,因為它允許你運行這些具體的測試,這些測試與你工作使用的代碼相關。在一個測試集中要運行所有的測試,你可以使用控制臺測試運行器,MSTEST.EXE。在這有大量的命令行選項,但是你僅僅需要” /testcontainer”選項,它指定了包含在測試中的程序集。另外,你可以使用”/resultfile”來指定結果文件的名稱。當使用 MSTEST.EXE時,你不必擔心VSMDI文件。在下一部分,我將論述一個MSBuild.EXE任務,它可以自動的運行在目錄結構中發現的所有測試,所以你可以避免和VSMDI文件一起使用。
你發現hardcoded路徑的第二個位置就是在TESTRUNCONFIG文件中;指向instrument和strong key文件的程序集就包含hardcoded路徑。我曾經指出了解決這個問題最好的辦法就是當你在不同的目錄結構下運行測試時使用一個不同的 TESTRUNCONFIG文件。因為沒有辦法創建一個新的TESTRUNCONFIG文件,你需要復制一個已經存在的文件到機器位置,然后使用 Visual Studio編輯它。如果驅動和上一級目錄不同,IDE和MSTEST.EXE講使用相關的路徑來處理TESTRUNCONFIG文件,但是,你必須手動的編輯它們。
因為它本身不是做代碼覆蓋的測試工具,所以沒有辦法阻止為來自命令行的已經編譯的程序集手動的執行代碼覆蓋。如果你使用ASP.NET進行工作,你需要從Visual Studio集成開發環境中做代碼覆蓋來創建合適的工具集,然后使它成為工具。
手動的做代碼覆蓋是一個三步的過程。第一步就是實現程序集以致你在它們中擁有代碼覆蓋鉤(hooks)。第二步就是開始監視進程,然后告訴它在哪編寫覆蓋文件。任何在監視進程運行時加載的已經實現的二進制將它們的覆蓋數據添加到輸出文件。最后一步就是關閉監視,編寫CONVERAGE文件。我已經制作出一個Converage.Targets文件,你可以和MSBuild一起使用來自動化這個過程。Figure 2顯示出一些文件;您可以從MSDN®Magazine網站下載到全部的文件。
當你瀏覽Figure 2時,你將看到在Bugslayer.Build.Task.DLL中我寫了一個任務來運行監視進程。Figure 3在Visual Studio中顯示了它。
當首次編寫Civerage.TARGETS時,我使用Exec任務執行VSPERFCMD /START:Coverage /OUTPUT:$(OutputCoverageFile),但是在做這時有一個嚴重的錯誤,MSBulid.EXE在調用時完全的掛起了。 VSPERFCMD.EXE產生出了真實的監視進程,VSPERFMON.EXE。如果你在命令提示符下運行VSPERFMON.EXE,進程將待在那吐出連接和進程中其它活動的信息,所以你不能從項目文件中直接的調用它。
這個問題出現在MSBuild.EXE中,滋生于某個事件,這個事件就是VSPERFMON.EXE從帶有bInheritHandles標記的 VSPERFCMD.EXE進程中產生到CreateProcess,并且設置為“真”。任何帶有繼承句柄開始的進程將在MSBuild.EXE下掛起。因此,我必須在任務中從ITask.Execute方法調用Process.Start,通過這樣操作才能使MSBuild.EXE下的所有事情正常運行。
與GenericTest和EXEs一起合作
如果你已經獲得一個基于EXE程序的存在的測試系統,在文檔中GenericTest類型的論述可能傷害您的好奇心。當在一個依賴運行九個EXE作為單元測試的批處理文件的項目上進行工作時,我可以使用GenericTest類型快速的包裝一些自動化過程中存在的代碼。雖然這也有一些catch。第一個小的障礙就是GenericTest允許0作為一個成功的來自EXE的返回值。那并不是一個很大的處理,但是考慮到GenericTest的高級特性,我很沮喪的看到與可接受的退出代碼區域一樣簡單的事情被遺漏了。
GenericTest的一個比較大的問題就是它是hardcoding的一個堡壘。幸運地,可以容易地指出相對路徑地文治。如果你地 GenericTest存在C:\FOO中,事實上測試將從C:\FOO\TestResults\_< Machine>_\Out開始。這樣,如果被GenericTest執行地EXE文件在C:\FOO中,你可以使用./././.EXE作為程序來執行。不幸地是,幾乎GenericTest中地其他事情從驅動器中被硬編碼。有趣地是外部目錄就是你地二進制每次運行時被復制到地地方。即使你改變了代碼,你可以重新運行測試地以前版本以重新生成問題。
一個便利地特性,GenericTest類型將捕獲定位到標準輸出地任何事情,在結果文件中提供一個運行日志。不幸的是,這看上去像是一個捕獲的問題,在那提取一些信息將導致測試驅動進程掛起。但是大部分測試程序在幾秒中內不會抽空輸出結果中的100行。
創建單元測試
當提到測試,Visual Studio真正的魔力就是當你在編輯器里右擊一個方法時,它可以奇妙的創建你得到的單元測試選項。這個特性非常好,可以很容易的快速添加單元測試。但是,我遇到了一個小的哲學問題,它讓你創建可以直接進入類并且訪問私有方法的單元測試。
針對允許測試工具直接調用私有的或受保護的方法的爭論就是它減化了測試(只寫很少的代碼),并且幫助擴寬了代碼覆蓋。這些爭論是很誘人的,但是我同意這個觀點,就是認為單元測試應該僅僅通過公共接口出現。單元測試是代碼的首次使用,你想朝著其他的怎么使用它的方向來調整測試。如果有一些私有方法,這些方法你在沒有short circuiting和直接的調用它們的情況下不能充分的測試,我必須知道是否代碼需要被注冊。為了阻止偶然的創建一個直接調用私有方法的單元測試,找到創建單元測試對話框,在右上角點擊過濾,然后清空顯示非公共項目。
我決不是一個絕對論者。我確定有一些示例,在示例里面它將幫助調用私有方法。但是,僅因為工具允許你做一些事情并不意味著你將依賴它。單元測試是測試的第一階段,它也是你開始white box測試的第一個位置。
使用NUnit
我有一些項目,在項目中我們已經在一個擴充NUint的測試系統做出了很大投資。(對.NET Framework 2.0起作用的新版本即將被發布)在一個示例中,我們想要代碼在NUnit和Visual Studio測試系統之間是便攜式的。當計劃這個時,我偶然發現很酷的一些事情,那就是需要最少的代碼改變,并且允許代碼與NUnit和Visual Studio一起工作。
我有一些項目,在項目中我們已經在一個擴充NUint的測試系統做出了很大投資。(對.NET Framework 2.0起作用的新版本即將被發布)在一個示例中,我們想要代碼在NUnit和Visual Studio測試系統之間是便攜式的。當計劃這個時,我偶然發現很酷的一些事情,那就是需要最少的代碼改變,并且允許代碼與NUnit和Visual Studio一起工作。
#if !NUNIT
using Microsoft.VisualStudio.TestTools.UnitTesting;
#else
using NUnit.Framework;
using TestClass = NUnit.Framework.TestFixtureAttribute;
using TestMethod = NUnit.Framework.TestAttribute;
using TestInitialize = NUnit.Framework.SetUpAttribute;
using TestCleanup = NUnit.Framework.TearDownAttribute;
#endif
我所需要做的就是使用NUnit測試屬性改變我的方法為TestMethod,然后我就有一個在兩種方式下工作的測試代碼。
TimeOutAttribute
包含決大部分屬性的文檔固然非常好。但是,最重要的屬性之一,TimeOutAttribute并沒有包含在API文檔中。當TESTRUNCONFIG 文件允許你指定單元測試的全部超時值時,TimeOutAttribute讓你指定一個單獨的測試可能花費的最大毫秒數。我發現 TimeOutAttribute在這些聯系數據庫的測試方法上沒有價值,所以我密切注視這些查詢。請注意,時間值包含一些測試運行器的時間。另外,機器的速度和性能將影響時間。使用你的測試進行實驗來看一下時間選擇是怎么在你的系統上工作的。
TestContext類我們只是簡要的涉及到,它也是被單元測試向導添加進來的TextContext屬性。主要的論述是關于當你正在使用 DataSourceAttribute時通過使用TestContext類獲得數據行。TestContext類有很多內容提供。文檔顯示出 TestContext類標記為抽象,但是事實上支持你的單元測試的源類型是UnitTestAdapterContext,它來自于 Microsoft.VisualStudio.QualityTools.Tips.UnitTest.Adapter.dl,你可以在< Visual Studio .NET install dir>\Common7\IDE\PrivateAssemblies 中發現這個DLL文件。你可以使用.NET Reflector查看UnitTestAdapterContext以此明白它是如何工作的。
或許這個類支持的最重要的方法就是WriteLine,你可以使用它添加額外的輸出到單獨的測試結果中。所有的編寫都將出現在報告的額外信息部分。為了發現什么測試正在運行或測試開始于什么目錄,你可以單個使用TestContext屬性域TestName和TestDir。最后,如果你想為所有或部分測試設置時間器,調用TestContext.BeginTimer和TestContext.EndTimer。在標準的控制臺輸出部分,時間數據將顯示在測試運行結果中。
為你寫代碼
我曾經提到,我已經包含了一個連同它的單元測試一起的真正單元。Bugslayer.Utility.DLL是一個我已經在項目之間拖拽的有用代碼集合。 ArgParser類是一個命令行論據分析類,這個類是基于在舊的.NET Framework SDK WordCount例子中的類。SystemMenuForm類是一個Windows窗體,這個窗體允許你在系統菜單中附加項目和作為常規時間回應點擊操作。
我之所以編寫GlobalMessageBox是因為每次使用一個消息框時我非常厭煩看到代碼分析錯誤,Specify MessageBoxOptions。規則規定當使用一個消息框時,你需要瀏覽類,然后明確是否RightToLeft屬性被設置為Yes。如果是,你必須調用合適的MessageBox.Show負載在MessageBoxOptions標記里通過。GlobalMessageBox為了我而關心這個,抑制錯誤和允許我的代碼在右到左的語言系統中正確的工作。
對于Bugslayer.Utility.DLL的完全單元測試位于Bugslayer.Utility\Tests\ Bugslayer.Utility.Tests目錄中。在各種各樣的CS文件中包括39個測試,提供了超過92%的代碼覆蓋。由于這是一個 Windows 窗體的單元,此單元提出了消息框和控件,你不能夠無人參與的運行它,但是它能夠在15秒之內運行。
對于Bugslayer.Utility.DLL的完全單元測試位于Bugslayer.Utility\Tests\ Bugslayer.Utility.Tests目錄中。在各種各樣的CS文件中包括39個測試,提供了超過92%的代碼覆蓋。由于這是一個 Windows 窗體的單元,此單元提出了消息框和控件,你不能夠無人參與的運行它,但是它能夠在15秒之內運行。
名稱和位置
MSTEST.EXE最不成對的部分就是它的輸出命名規范。如果你使用/RUNCONFIG選項來操作一個TESTRUNCONFIG文件,輸出文件將使用在那個文件中規定的命名規范。如果你不使用/RUNCONFIG,或者設置為默認,所有的輸出被寫到.\TestResults\< user>_ 。我建議使用能夠快速識別的名稱。
MSTEST.EXE提供了/RESULTSFILE選項,但是這將導致輸出文件名稱丟失時間戳。另外,如果指定到/RESULTSFILE的文件名稱退出,MSTEST.EXE將會失敗。我所希望的就是指定一個我所集中工作的細節的名稱,但是不需要手動的添加時間戳。
你可能會想可能的解決方法就是使用VSMDI測試源數據文件,這個文件你過去在測試管理器窗口中看到。事實上,MSTEST.EXE沒有/TESTMETADATA選項來加載和運行測試。問題是你僅僅能指定一個VSMDI文件。
一個可能的解決方案就是創建一個單獨的VSMDI文件,這個文件在你的代碼里面導入所有其它的VSMDI文件。那的確可以工作,但是它也呈現出另外一個維護任務來回憶每一次你添加新的測試到代碼中。
值得注意的是當運行VSMDI文件時你不能夠告訴IDE或是MSTEST.EXE將輸出文件放在什么位置。輸出文件指向了VSMDI文件貯存的目錄。建議在一個目錄中保存測試,這個目錄在版本控制里源代碼之下,以致如果你共享項目,所有的測試代碼將會跟隨它。
由于VSMDI文件作為每一次測試的一部分,并且沒有一種方式來集中輸出,輸出將圍繞著你的源代碼被分散。這并不是一個很大的處理,但是它意味著你必須手動的整理源樹。在處理一些測試運行結果之后,我想要一種簡單的方法來處理這個。
一個更好的MSTEST.EXE
基于此次論述,我明白了有四個特性我想添加到MSTEST.EXE。第一個是一種方法用來動態的發現所有的單元測試,然后就像小的smoke測試一樣運行它們。第二個是一個簡單的方法用來識別出相似的測試運行而不僅僅是讀時間戳。第三個就是確保所有的測試輸出定位到一個單一的位置。最后,我想要一種非常容易的方法來除去無關的測試運行而不管它們在源樹中的什么位置。
這些需求強烈要求MSBuild。Bugslayer.Build.Tests.DLL中的MSTestTask包裝MSTEST.EXE所以你能夠獲得所有的控制。就像你看到的代碼,你將注意到它是從ToolTask類獲得的,并且是由Microsoft.Build.Utilities.DLL程序集產生的。當編寫一個包裝了一個命令行工具(因為它做了大部分的提高)build任務時,這個任務,ToolTask就是你想要使用的。
對于一些工具,你所需要做的就是定義你唯一的屬性,重載三個方法和一個屬性。屬性是ToolName,它返回了工具的可執行名稱。 GenerateFullPathToTool方法返回了完全的驅動,路徑和文件名稱到工具本身。為了驗證這些參數,你需要重載 ToolTask.ValidateParameters方法,如果一切正常的話,返回值為真。為了編譯真實的命令行到工具中,重載 ToolTask.GenerateCommandLineCommands然后使用CommandLineBuilder類或者是我對它做的簡單的擴展 ExtendedCommandLineBuilder。
為所有可能的命令行參數運行MSTEST.EXE/?。當它指定輸出文件名稱的時候需要ResultsFile。你也需要設置TestMetaData參數或者TestContainer參數以此分別的顯示出源數據文件或者測試容器。
為所有可能的命令行參數運行MSTEST.EXE/?。當它指定輸出文件名稱的時候需要ResultsFile。你也需要設置TestMetaData參數或者TestContainer參數以此分別的顯示出源數據文件或者測試容器。
但是,我對于MSTestTask的長期計劃就是擴展測試屬性以允許通配符能夠為測試名稱的執行被通過。那將允許你容易的執行僅符合具體前綴的那些測試。借助在TestContainer屬性中被通過的程序集,這工作將僅僅是一系列的反射,查找擁有TestClassAttribute的類庫和一些符合規范表達的帶有TestMethodAttribute的方法。
MSTestTask其他的操作部分來自于RunTests.Targets文件,你可以在.\Build包含源代碼的目錄中找到這個文件。它包括了非??岬腅xecuteAllTests對象,這個對象在你指定的目錄中開始,在整個結構中查找所有的單元測試,GenericTests,WebTests 和OrderedTests,然后執行它們。你可以認為ExecuteAllTests對象對于單元測試是一個來說自動衰退測試。當你添加新的測試時,它將自動的執行它們。在代碼下載中包含的RunTests.Targets代碼非常明智的使用排除文件來獲得我們希望的東西。為了查看一個使用中的 RunTests.Targets例子,查看一下SmokeTest.proj,它為所有的代碼顯示出smoke測試。
在.\Build目錄中最后的TARGETS文件是MSTestCleanUp.Targets。就像它名稱所暗示的那樣,它的職責就是查找包含 TestResults的所有目錄作為一個路徑然后刪除它們。它是使用轉換的一個很好的例子,就像MSBuild中的RemoveDuplicates任務一樣。使用MSTestClean-Up.Targets,你就不用怕其他的文件毀壞你的源目錄。
掩飾,包裝
如果你不說,我將對Visual Studio 2005中新的單元測試工具非常興奮。像ASP.NET和DataGrids這樣的程序在此書中受到了所有的關注,但是當你努力按時獲得程序時,測試工具將會帶來很大的影響。我可以保證,你在使用測試工具上花費的時間越多,你的代碼將會變的更好。
Tip 73 你可以控制一個單元測試默認的編程語言和當一個新的單元測試被單元測試向導創建后哪些條目能被添加到里面。來到選項對話框,擴展測試工具節點,然后轉到測試項目屬性頁面。在那,你將看到默認的測試項目類型combobox和每個語言類型的默認文件選擇。如果你像我一樣,在你創建了第二個單元測試后你將清除 “About Test Projects”介紹文件。
Tip 74 試圖將一個程序集中的所有的單元測試保存到一個單一的測試程序集。那個一對一的映射來自于維護的觀點。但是,當程序集增長時,測試數量將會變得非常大。我喜歡將前綴放到測試方法的名稱上,因為這個它們測試的特性有關。這樣可以很容易的分組。例如,在Bugslayer.Utility.Tests.DLL 程序集中,關于GlobalMessageBox類的測試開始于”GNB_”
原文轉自:http://www.anti-gravitydesign.com