軟件測試中強化Visual Studio 單元測試
使用單元測試向導在一個新的項目上添加測試是一件非常容易的事情。這個便利的特點可以節省你數百個小時的打字時間(微軟的開發者為此應收取很多費用)。但是,一些事情在幕后發生了,并且導致你很悲傷:路徑是hardcoded!這是一個問題,例如,當你移動一個測試到另一臺機器或是目錄時就會發生。我希望一個Visual Studio 2005的Service Pack允許你為測試設置一個開始路徑,hardcoded路徑將使用相對路徑。目前,但是解決hardcoded路徑的容易方式就是在團隊中有一個慈善的獨裁者,它總是判斷和頒布所有的驅動和路徑。如果它是一個小的項目,這種解決方案是很好的。另一個,一些實用的工作區在你的路徑中將引用% testdeploymentdir%環境變量。當測試運行時它被設置。
Hardcoded路徑出現的第一個地方就是在VSMDI文件中,這個文件是一個大的包裝皮里面包含簡單的測試列表。當你打開一個VSMDI文件,然后它不能找到測試集或者TESTRUNCONFIG文件,你將被提示為這些項目指出位置。我發現有意思的事情是下一次我再打開相同的VSMDI文件時,它會發現所有的東西。很明顯,更新的路徑一定存儲再某個地方,但是VSMDI文件本身并沒有被改變。我再VSMDI文件駐留的目錄中發現一個隱藏文件,名稱為 name<filename>.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\<User>_< Machine>_<TimeStamp>\Out開始。這樣,如果被GenericTest執行地EXE文件在C:\FOO中,你可以使用./././<name>.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。在標準的控制臺輸出部分,時間數據將顯示在測試運行結果中。
原文轉自:http://www.anti-gravitydesign.com